数组——双指针:75.颜色分类
题目链接
75.颜色分类
题目重构
包含0,1,2共 n
个元素的数组 nums
,原地对它们进行排序,使得相同的元素相邻,并按照0、1、2的顺序排列。
必须在不使用库内置的 sort 函数的情况下解决这个问题。
方法一:单指针
思路
可以对数组进行两次遍历,第一次将所有0移到数组头部,第二次将所有1移到数组头部。这样2就都在数组尾部,完成排序。
设一个ptr
指针作为排好序的元素范围,由于我们要对整个元素进行排序,所以我们使用swap
函数来交换元素。这里也是用到快慢指针
的思想,我们用循环里的变量i
作为快指针遍历数组,ptr
为慢指针,当遍历到0时,便让两个指针指向的元素交换,同时让ptr
自增。
当处理完所有0,此时我们有ptr
个元素0已经完成排序,不需要动了,那么我们从ptr
的位置开始处理元素1。同样用快慢指针来排序。最后完成排序。
代码
int ptr = 0;
for (int i = 0; i < nums.size(); ++i) {if (nums[i] == 0) {swap(nums[i], nums[ptr++]);}
}
for (int i = ptr; i < nums.size(); ++i) {if (nums[i] == 1) {swap(nums[i], nums[ptr++]);}
}
评价
- 时间复杂度:
O(n)
- 第一个
for
循环遍历整个数组一次,执行n
次 操作。 - 第二个
for
循环从ptr
开始,最多也遍历n
次(实际上少于n
,因为ptr
≥ 0) - 两个循环是顺序执行,不是嵌套,因此总时间复杂度为:O(n)+O(n)=O(n)O(n) + O(n) = O(n)O(n)+O(n)=O(n)
- 第一个
- 空间复杂度
O(1)
:所有操作都是原地进行的,只使用了常数级别的额外空间(如临时变量i
,ptr
等)。
方法二:双指针
思路
可以多用一个指针,只需要一次遍历。两个指针分别交换0和1。p0
和p1
分别指向下一个 0 和 1 应该被放置的位置。初始均指向0。
当我们遍历数组时:
- 遇到1时,与
nums[p1]
交换,然后右移p1
,p0
不动。 - 遇到0时,与
nums[p0]
交换- 应当注意到,由于
p0
和p1
分别指向下一个 0 和 1 应该被放置的位置,当我们进行过1的交换时,p1
会大于p0
,即在p0
指向的位置开始到p1 - 1
的位置都是连续的1。那么这时候遇到0,我们交换可能会将1交换到尾部。 - 因此我们需要加一个应对方法,也就是当遇到
p0 < p1
时,我们要把nums[i]
和nums[p1]
交换。也就是说,我们进行了这样一个操作,把原本最前面的1移到了最后一个1后面。最后,由于0和1部分都往后移了,所以两个指针都自增。
- 应当注意到,由于
代码
int n = nums.size();
int p0 = 0, p1 = 0;
for (int i = 0; i < n; ++i) {if (nums[i] == 1) {swap(nums[i], nums[p1++]);} else if (nums[i] == 0) {swap(nums[i], nums[p0]);if (p0 < p1) {swap(nums[i], nums[p1]);}++p0;++p1;}
}
评价
- 时间复杂度:
O(n)
,其中n
是数组nums
的长度。 - 空间复杂度:
O(1)
。
方法三:双指针升级版
思路
方法二的两个指针都是从数组头部开始,处理0时需要考虑到已处理的所有1的影响。我们可以把两个指针分置两头。即使用p0
来交换0,p2
交换2,p0
的初始值仍然为 0,而p2
的初始值为 n − 1
。我们需要把所有0交换到数组头部,把所有2交换到数组尾部。
由于指针p2
是从右往左走,因此,当我们从左向右遍历超过p2
的位置时,就可以停止遍历了。
- 如果找到0,与前面的方法一样,只需要与
nums[p0]
交换,再p0++
就行 - 如果找到2,我们把
nums[i]
交换到尾部,p2--
,但原本的nums[p2]
可能为2或者0,不能马上把遍历指针移走,否则这个2就会留在头部。我们要再判断这个新的nums[i]
是否是2,是的话继续执行交换,自增循环。等到把所有的2处理完后,再处理0。
代码
int n = nums.size();
int p0 = 0, p2 = n - 1;
for (int i = 0; i < n; ++i) {while (i <= p2 && nums[i] == 2) {swap(nums[i], nums[p2]);--p2;}if (nums[i] == 0) {swap(nums[i], nums[p0]);++p0;}
}
评价
- 时间复杂度:
O(n)
—— 仅一次遍历 - 空间复杂度:
O(1)
—— 原地操作