给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
注意:
不能使用代码库中的排序函数来解决这道题。
示例:
输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]
进阶:
一个直观的解决方案是使用计数排序的两趟扫描算法。
首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。
你能想出一个仅使用常数空间的一趟扫描算法吗?
这道题是三路快排的变种:1作为枢轴值,我们要得到两个分界点: 0区域的右边界zero,2区域的左边界two
遍历nums,当前cursor为i:
当nums[i]的值为枢轴值1时,i++;
当nums[i]的值为2时,two的值先减1,而后交换nums[i]与nums[two],此时再观察nums[i]的值;
当nums[i]的值为0时,zero++,而后交换nums[i]与nums[zero],i++;
当 i = two时,结束循环
最好情况下,枢轴值刚好是中值,因此二分树很均衡,深度为log(n),每次二分点的寻找需要遍历整个数组,对于每一层来说,就是遍历n个元素因此复杂度O(nlog(n))
最坏情况下, 每次待排数组都只比上一次少一个元素,为斜树,一共要n-1次递归,对于第i次排序,要遍历n-i个元素,因此复杂度1+2+…+n-1 = n(n-1)/2,为O(n2)
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
# 通常三路快排right_bound_of_range_less_than_pivot初始为0,它对应的值为枢轴值
# 这样while循环结束后,0位置上的值依然是保持不动的,
# 需要把它交换到当前right_bound_of_range_less_than_pivot上面
# 我们这道题是已知枢轴值为1,而不是用初始0位置上的值作为枢轴值
right_bound_of_range_less_than_pivot = -1
left_bound_of_range_larger_than_pivot = len(nums)
# 遍历的右边界是动态变化的,因此用while
current_index = right_bound_of_range_less_than_pivot + 1
while current_index < left_bound_of_range_larger_than_pivot:
if nums[current_index] == 0:
right_bound_of_range_less_than_pivot += 1
self.swap(nums, current_index, right_bound_of_range_less_than_pivot)
current_index += 1
elif nums[current_index] == 2:
left_bound_of_range_larger_than_pivot -= 1
self.swap(nums, current_index, left_bound_of_range_larger_than_pivot)
else:
current_index += 1
def swap(self, nums, i, j):
nums[i], nums[j] = nums[j], nums[i]
return nums
参考资料
快速排序算法
def quick_sort(nums):
q_sort(nums, 0, len(nums)-1)
def q_sort(nums, l, r):
if l >= r:
return
# 对于数组nums以l和r为边界的一段来说
# 在中间选则一个枢轴点,使得它左边的数都比它小,它右边的数都比它大
p_index = partition(nums, l, r)
# 这样就可以对左边进行递归排序,使得枢轴点左边为有序的
q_sort(nums, l, p_index-1)
# 同样也可以对右边进行递归排序,使得枢轴点右边为有序的
q_sort(nums, p_index+1, r)
def partition(nums, l, r):
# 左边界对应的值定义为枢轴值
pivot = nums[l]
# 令枢轴点初始化为左边界
pivot_index = l
# 从左边界的右边一位开始遍历到右边界
for i in range(l+1, r+1):
# 如果对应的数比枢轴值小
if nums[i] < pivot:
# 就把枢轴点右移一位
pivot_index += 1
# 并且把那个小值放到当前枢轴点上, 把当前枢轴点上的值换过去
# 每个当前枢轴点上的值都是小于枢轴值的
swap(nums, i, pivot_index)
# 最后将枢轴值放到当前枢轴点,当前枢轴点上的值换到左边界
# 使得枢轴点上的值等于枢轴值,枢轴点左侧的数都比它小,右侧的数都比他大
swap(nums, l, pivot_index)
return pivot_index
def swap(nums, i, j):
nums[i], nums[j] = nums[j], nums[i]
return nums
三路快排算法
def quick_sort(nums):
q_sort3ways(nums, 0, len(nums)-1)
def q_sort3ways(nums, l, r):
if l >= r:
return
# 获得两个点,
# 第一点及其左边全部<枢轴值
# 第二个点及其右边全部>枢轴值
right_bound_of_range_less_than_pivot, \
left_bound_of_range_larger_than_pivot = partition3ways(nums, l, r)
# 对左右两段递归调用本函数,使得左右两段分别有序
q_sort3ways(nums, l, right_bound_of_range_less_than_pivot)
q_sort3ways(nums, left_bound_of_range_larger_than_pivot, r)
def partition3ways(nums, l, r):
# 左边界的值定义为枢轴值
pivot = nums[l]
# 左枢轴点初始化为左边界
right_bound_of_range_less_than_pivot = l
# 右枢轴点初始化为右边界的右边一位
left_bound_of_range_larger_than_pivot = r + 1
# 从左边界右边一位开始往右遍历
i = l + 1
while i < left_bound_of_range_larger_than_pivot:
# 如果取值小于枢轴值就把左枢轴点右移一位,并且这个小值放到当前左枢轴点,
# 将当前左枢轴点上的值(等于枢轴值)放到当前遍历点,
# 因此当前左枢轴点的右边一位一直到当前遍历点上的所有值都是等于枢轴值的
# 既然当前遍历点上的值已知,此时就可以继续看下一个点
if nums[i] < pivot:
right_bound_of_range_less_than_pivot += 1
swap(nums, i, right_bound_of_range_less_than_pivot)
i += 1
# 如果取值大于枢轴值就把右枢轴点左移一位,并且这个大值就放到当前右枢轴点
# 当前右枢轴点上的值放到当前遍历点上,但是这个值是未知大小的,因此不继续看下一个点而是判断这个点
elif nums[i] > pivot:
left_bound_of_range_larger_than_pivot -= 1
swap(nums, i, left_bound_of_range_larger_than_pivot)
# 如果取值等于枢轴值,则继续看下一个点
else:
i += 1
# 此时左枢轴点上的值是小于枢轴值的,需要将当前左枢轴点上的值放到左边界,将左边界上的枢轴值放到当前左枢轴点,
# 使得左枢轴点左边的值全部小于枢轴值
swap(nums, right_bound_of_range_less_than_pivot, l)
# 既然当前左枢轴点上的值等于枢轴值,它左边一位是小于枢轴值的,故返回这个位置
# 当前右枢轴点及其右边的值是大于枢轴值的
return right_bound_of_range_less_than_pivot-1, left_bound_of_range_larger_than_pivot
def swap(nums, i, j):
nums[i], nums[j] = nums[j], nums[i]
return nums