您的位置:首页 > 编程语言 > C语言/C++

Leetcode 075 颜色分类 Python C++ 史上最详细题解系列(多解法)

2018-08-12 08:02 756 查看


每天更新一道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中讲过,忘干净了。。

阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: