Leetcode 075 颜色分类 Python C++ 史上最详细题解系列(多解法)
每天更新一道python or C++ leetcode题,力求讲解清晰准确,客官们可以点赞或者关注。
题目:
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
注意:
不能使用代码库中的排序函数来解决这道题。
示例:
输入: [2,0,2,1,1,0] 输出: [0,0,1,1,2,2]
进阶:
- 一个直观的解决方案是使用计数排序的两趟扫描算法。
首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。 - 你能想出一个仅使用常数空间的一趟扫描算法吗?
首先给出直观的解决方案,也就是进阶里已经写出来了的。
[code]class Solution: def sortColors(self, nums): """ :type nums: List[int] :rtype: void Do not return anything, modify nums in-place instead. """ num0 = 0 num1 = 0 num2 = 0 i = 0 while i < len(nums): if nums[i] == 0: num0 += 1 elif nums[i] == 1: num1 += 1 elif nums[i] == 2: num2 += 1 i += 1 j = 0 while j < len(nums): if num0 > 0: nums[j] = 0 num0 -= 1 elif num1 > 0: nums[j] = 1 num1 -= 1 elif num2 > 0: nums[j] = 2 num2 -= 1 j += 1
这个算法的话可以说基本练过leetcode的人都能写得出。
接下来介绍进阶的做法:三向切分。
首先直观地从名字上去理解算法。
这个算法的目的是使的数组分成3个区间,每个区间的元素是相同的,而且这3个区间按大小排序。
好,知道了算法的目的,怎么实现呢?
算法过程:
1.初始化2个指针,一个指向数组的开头-1,一个指向结尾+1
2.zero_end是指的是0的区间的边界所在的index,two_begin是2的区间的边界所开始的index.
3.遇到一个0,我们把那个0和nums[++zero_end]交换,注意这个时候zero_end加1了,同时如果是2,也就是最后一个区间的元素,我们需要与two_begin上所在的元素交换,同时two_begin也减去了1,如果是1,也就是中间区间的元素,则跳过。
[code]class Solution { public: // 交换两个数函数 void swap(int &a, int &b) { int temp = a; a = b; b = temp; } void sortColors(vector<int> &nums) { int zero_end = -1; int two_begin = nums.size(); int i = 0; while (i < two_begin) { if (nums[i] == 0 && i != ++zero_end) { swap(nums[i], nums[zero_end]); } else if (nums[i] == 2 && i != --two_begin) { swap(nums[i], nums[two_begin]); } else i++; } } };
可能很多人不知道为什么这样能够排序,我这里稍微讲个例子:
[1,...0,...]假设有这么一个数组,第一位是1,第k位出现了第一个0,根据我们的算法,我们会跳过1,然后当我们遇到第一个0的时候,我们会把1和0交换,也就是说变成[0,...1,...],这时候i位变成1了,继续跳过,所以我们的指针能不断前进。
更强更简洁的代码:
接下来要介绍的算法,已经超神了。它不是那么好理解,但它又有三向切分的影子。、
读代码前一定要搞懂:
1.i,j, k 分别指向当前遍历的数组的最后一个0,1,2的位置。
比如,当前已处理好的部分数组是 0 0 0 0 1 1 1 2 2 2 *,那么i,j,k分别指向最后一个0,1,2。m指向*,*号为待处理元素,
2.++k的结果是k+1
3.如果i,j,k一样,则它们对应的赋值则有可能覆盖对方的赋值。
4.如果没懂,多读几遍
所以我们的算法就是,
遇到0,1,2把i,j,k后面1位的值赋成对应的值即可。
[code]class Solution { public: void sortColors(vector<int>& nums) { int i = -1,j = -1,k = -1;//注意i,j,k的含义 int m; for(m = 0; m < nums.size();m++){ if(nums[m] == 0){ nums[++k] = 2; nums[++j] = 1; nums[++i] = 0; } else if(nums[m] == 1){ nums[++k] = 2; nums[++j] = 1; } else { nums[++k] = 2; } } } };
总结:
学习了三向切分的2种实现形式,
一种是常规的设置各区间边界的解法:
1.先设置第一个区间的末尾边界,再设置第三个区间的开头边界。
2.遇到第一区间的元素(0),与第一区间末尾边界交换,末尾边界+1;遇到第2区间的元素,跳过;遇到第三区间的元素,与第三区间开头边界交换,并加1。
一种是设置三个指针的三向切分:
i指向第一小元素的最后一个index,j指向第2小元素的最后一个index, k指向第三小元素的最后一个index。
遇到第一小的元素,把i,j,k都往后推一步,并且在赋值。
遇到第2小的元素,由于只会影响2,3区间,于是只用把,j,k往后推一步再赋值。
遇到第3小的元素,只会影响3区间,把k往后推一步赋值。
1区间指第一个区间,也就是最小的区间。
今天总结干货很多,但同时也发现了自己的基础不牢,因为三向切分在《算法》4中讲过,忘干净了。。
阅读更多- Leetcode 077 组合 Python C++ 史上最详细题解系列
- Leetcode 80 删除排序数组中的重复项 II Python C++ 史上最详细题解系列
- Leetcode 078 子集 Python C++ 史上最详细题解系列
- Leetcode 81 搜索旋转排序数组 II Python C++ 史上最详细题解系列
- Leetcode 83 删除排序链表中的重复元素 Python C++ 史上最详细题解系列
- Leetcode 076 最小覆盖子串 Python C++ 史上最详细题解系列
- Leetcode 079 搜索单词 Python C++ 史上最详细题解系列
- Leetcode 82 删除排序链表中的重复元素 II Python C++ 史上最详细题解系列
- Leetcode 312 打气球 Burst Balloons C++ 史上最详细题解系列
- [LeetCode]题解(python):075-Sort Colors
- [LeetCode] Leetcode 题解索引 (C++/Java/Python/Sql)
- leetcode_效率题解_[python/C++]_147. Insertion Sort List(链表插入排序)
- LeetCode题解汇总(C++ Java Python,含题目翻译)
- leetcode_效率题解_[python/C++]_21. Merge Two Sorted Lists(合并2个有序链表)
- [LeetCode系列] 二叉树最大深度求解问题(C++递归解法)
- LeetCode 144 — Binary Tree Preorder Traversal(C++ Java Python)
- LeetCode题解:Range Sum Query - Immutable(C++版本)
- leetcode_[Python/C++]_3_Longest Substring Without Repeating Characters(不重复子串最大长度)
- [LeetCode]题解(python):032-Longest Valid Parentheses
- Leetcode_c++: Sort Colors (075)