给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

必须在不使用库的sort函数的情况下解决这个问题。


示例 1:

输入:nums = [2,0,2,1,1,0]

输出:[0,0,1,1,2,2]

示例 2:

输入:nums = [2,0,1]

输出:[0,1,2]


提示:

n == nums.length

1 <= n <= 300

nums[i] 为 0、1 或 2


进阶:

你可以不使用代码库中的排序函数来解决这道题吗?

你能想出一个仅使用常数空间的一趟扫描算法吗?

【分析】

本题是经典的荷兰国旗????????问题,根据题目提示,我们可以统计出数组中0,1,2的个数,再根据它们的数量,重写整个数组。这种方法较为简单,也很容易想到。而这里会介绍两种基于指针进行交换的方法。

方法一:单指针 

可以考虑对数组进行两次遍历。在第一次遍历中,我们将数组中所有的0交换到数组的头部。在第二次遍历中,我们将数组中所有的1交换到头部的0之后。此时,所有的2都出现在数组的尾部,这样我们就完成了排序。

具体地,我们使用一个指针ptr表示头部的范围,ptr中存储了一个整数,表示数组nums从位置0到位置ptr-1都属于头部。ptr的初始值为0,表示还没有数处于头部。

在第一次遍历中,我们从左向右遍历整个数组,如果找到了0,那么就需要将0与头部位置的元素进行交换,并将头部向后扩充一个位置。在遍历结束之后,所有的0都被交换到头部的范围,并且头部只包含0。

在第二次遍历中,我们从头部开始,从左向右遍历整个数组,如果找到了1,那么就需要将1与头部位置的元素进行交换,并将头部向后扩充一个位置,在遍历结束后,所有的1都被交换到头部的范围,并且都在0之后,此时所有的2都分布在头部之外的位置。排序完成。

时间复杂度:O(n),n为数组nums的长度。

空间复杂度:O(1)。

class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
ptr = 0 # 定义指针ptr表示头部的范围,ptr中用于存储一个数组,表示数组nums从位置0到位置ptr-1都属于头部。其初始值为0,表示还没有数字处于头部之中。
# 将数字0置于头部。
for i in range(n):
if nums[i] == 0:
nums[i], nums[ptr] = nums[ptr], nums[i]
ptr += 1
# 将数字1置于头部,这个头部范围在0之后。
for i in range(ptr, n):
if nums[i] == 1:
nums[i], nums[ptr] = nums[ptr], nums[i]
ptr += 1

方法二 双指针

方法一需要两次遍历,那么我们是否可以仅使用一次遍历呢?我们可以额外使用一个指针,使用两个指针分别来交换0和1。

具体地,我们用指针p0来交换数字0,用指针p1来交换数字1,初始值都为0,当我们从左向右遍历整个数组时:

(1)若找到了1,那么将其与nums[p1]进行交换,并将p1向后移动一位,这与方法一相同。

(2)如果找到了0,那么将其与nums[p0]进行交换,并将p0向后移动一位,这样做是正确的吗?我们可以注意到,因为连续的0之后是连续的1,因此如果我们将0与nums[p0]进行交换,那么我们可能会把一个1交换出去。当p0<p1时,我们已经将一些1连续地放在头部,此时一定会把一个1交换出去,导致答案错误。因此,如果p0<p1,那么我们需要再将nums[i]与nums[p1]交换,其中i是当前遍历到的位置,在进行了第一次交换后,nums[i]的值为1,我们需要将这个1放到头部的末端。在最后,无论是否有p0<p1,我们都需要将p0和p1均向后移动一个位置,而不是只将p0向后移动一个位置。

class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
p0, p1 = 0, 0
# 将数字0置于头部。
for i in range(n):
if nums[i] == 1:
nums[i], nums[p1] = nums[p1], nums[i]
p1 += 1
elif nums[i] == 0:
nums[i], nums[p0] = nums[p0], nums[i]
# 当p0 < p1,
if p0 < p1:
nums[i], nums[p1] = nums[p1], nums[i]
p0 += 1
p1 += 1

# 复杂度分析
# 时间复杂度:O(n),其中 n 是数组nums 的长度。
# 空间复杂度:O(1)。

方法三:双指针

与方法二类似,我们也可以考虑使用指针p0来交换数字0,使用指针p2来交换数字2。此时,p0的初始值仍然为0,而p2的初始值为n-1。在遍历过程中,我们需要找出所有的0将其交换至数组头部,并且找出所有的2将其交换至数组的尾部。

由于此时其中一个指针p2是从右向左移动的,因此当我们在从左向右遍历整个数组时,如果遍历到的位置超过了p2,那么就可以直接停止遍历了。

具体地,我们从左向右遍历整个数组,设当前遍历到的位置为i,对应的元素值为nums[i]:

(1)若找到了0,那么与前面两种做法类似,将其与nums[p0]进行交换,并将p0向后移动一位;

(2)若找到了2,那么将其与nums[p2]进行交换,并将p2向前移动一个位置。

这样做对吗?可以发现,对于第二种情况,当我们将nums[i]与nums[p2]进行交换后,新的nums[i]可能仍然是2,也可能是0。然而此时我们已经结束了交换,开始遍历下一个元素nums[i+1],不会再考虑nums[i]了,这样我们就会得到错误的答案。

因此当我们找到2时,需要不断地将其与nums[p2]进行交换,直到新的nums[i]不为2。此时,如果nums[i]为0,那么对应着第一种情况;如果nums[i]为1,那么就不需要进行任何后续操作。

class Solution:
def sortColors(self, nums: List[int]) -> None:
n = len(nums)
p0, p2 = 0, n - 1
i = 0
while i <= p2:
while i <= p2 and nums[i] == 2:
nums[i], nums[p2] = nums[p2], nums[i]
p2 -= 1
if nums[i] == 0:
nums[i], nums[p0] = nums[p0], nums[i]
p0 += 1
i += 1


天雨虽宽,不润无根之草。 佛门虽广,不渡无缘之人。