英文原文
Given an array nums
with n
objects colored red, white, or blue, sort them in-place so that objects of the same color are adjacent, with the colors in the order red, white, and blue.
We will use the integers 0
, 1
, and 2
to represent the color red, white, and blue, respectively.
You must solve this problem without using the library's sort function.
Example 1:
Input: nums = [2,0,2,1,1,0] Output: [0,0,1,1,2,2]
Example 2:
Input: nums = [2,0,1] Output: [0,1,2]
Example 3:
Input: nums = [0] Output: [0]
Example 4:
Input: nums = [1] Output: [1]
Constraints:
n == nums.length
1 <= n <= 300
nums[i]
is0
,1
, or2
.
Follow up: Could you come up with a one-pass algorithm using only constant extra space?
中文题目
给定一个包含红色、白色和蓝色,一共 n
个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0
、 1
和 2
分别表示红色、白色和蓝色。
示例 1:
输入:nums = [2,0,2,1,1,0] 输出:[0,0,1,1,2,2]
示例 2:
输入:nums = [2,0,1] 输出:[0,1,2]
示例 3:
输入:nums = [0] 输出:[0]
示例 4:
输入:nums = [1] 输出:[1]
提示:
n == nums.length
1 <= n <= 300
nums[i]
为0
、1
或2
进阶:
- 你可以不使用代码库中的排序函数来解决这道题吗?
- 你能想出一个仅使用常数空间的一趟扫描算法吗?
通过代码
高赞题解
方法一:排序
数组里只包含 0、1、2,因此可以对数组排序,排序以后,所有的 0 就被摆放在了一起,所有的 1 就被摆放在了一起,所有的 2 就被摆放在了一起。
如果排序方法使用的是快速排序、归并排序,时间复杂度为 $O(N \log N)$。
又由于数组里只包含 0、1、2,还可以使用计数排序,时间复杂度为 $O(N)$。
(参考代码省略)
方法二:partition
题目最后给出的「进阶」要求,其实考察的是「快速排序」的子过程 partition,即:通过一次遍历,把数组分成三个部分。
写代码的时候需要注意到设置的变量以及区间的定义,也就是 循环不变量。循环不变量 简单说就是在循环的过程中保持不变的性质,这个性质是人为根据需要解决的任务定义的。
对 循环不变量 的简单认识:
- 变量的值是变化的,但是保持不变的性质,就是循环不变量;
- 这里的「量」是一些人为定义的、可以判断真假的语句,在循环开始前、循环的过程中、循环结束以后,都为真;
- 这里的「循环」是广义上的,并不一定指「循环」,也有可能是在「递归」的过程中。
下面给出两版代码,循环不变量我们作为注释写在代码中。不同的定义决定了:初始化时变量的取值、循环的过程中操作的先后顺序、循环结束的条件。
在本题视频题解:75. 颜色分类(官方题解) 里,我有详细讲解每一行代码的意思。
参考代码 1:
[]import java.util.Arrays; public class Solution { public void sortColors(int[] nums) { int len = nums.length; if (len < 2) { return; } // all in [0, zero) = 0 // all in [zero, i) = 1 // all in [two, len - 1] = 2 // 循环终止条件是 i == two,那么循环可以继续的条件是 i < two // 为了保证初始化的时候 [0, zero) 为空,设置 zero = 0, // 所以下面遍历到 0 的时候,先交换,再加 int zero = 0; // 为了保证初始化的时候 [two, len - 1] 为空,设置 two = len // 所以下面遍历到 2 的时候,先减,再交换 int two = len; int i = 0; // 当 i == two 上面的三个子区间正好覆盖了全部数组 // 因此,循环可以继续的条件是 i < two while (i < two) { if (nums[i] == 0) { swap(nums, i, zero); zero++; i++; } else if (nums[i] == 1) { i++; } else { two--; swap(nums, i, two); } } } private void swap(int[] nums, int index1, int index2) { int temp = nums[index1]; nums[index1] = nums[index2]; nums[index2] = temp; } }
[]from typing import List class Solution: def sortColors(self, nums: List[int]) -> None: """ Do not return anything, modify nums in-place instead. """ # all in [0, zero) = 0 # all in [zero, i) = 1 # all in [two, len - 1] = 2 def swap(nums, index1, index2): nums[index1], nums[index2] = nums[index2], nums[index1] size = len(nums) if size < 2: return zero = 0 two = size i = 0 while i < two: if nums[i] == 0: swap(nums, i, zero) i += 1 zero += 1 elif nums[i] == 1: i += 1 else: two -= 1 swap(nums, i, two)
[]#include <iostream> #include <vector> using namespace std; class Solution { public: void sortColors(vector<int> &nums) { int size = nums.size(); if (size < 2) { return; } // all in [0, zero) = 0 // all in [zero, i) = 1 // all in [two, len - 1] = 2 int zero = 0; int two = size; int i = 0; while (i < two) { if (nums[i] == 0) { swap(nums[zero], nums[i]); zero++; i++; } else if (nums[i] == 1) { i++; } else { two--; swap(nums[i], nums[two]); } } } };
复杂度分析:
- 时间复杂度:$O(N)$,这里 $N$ 是输入数组的长度;
- 空间复杂度:$O(1)$。
参考代码 2:
[]public class Solution { public void sortColors(int[] nums) { int len = nums.length; if (len < 2) { return; } // all in [0, zero] = 0 // all in (zero, i) = 1 // all in (two, len - 1] = 2 // 为了保证初始化的时候 [0, zero] 为空,设置 zero = -1, // 所以下面遍历到 0 的时候,先加,再交换 int zero = -1; // 为了保证初始化的时候 (two, len - 1] 为空,设置 two = len - 1 // 所以下面遍历到 2 的时候,先交换,再减 int two = len - 1; int i = 0; // 当 i == two 的时候,还有一个元素还没有看, // 因此,循环可以继续的条件是 i <= two while (i <= two) { if (nums[i] == 0) { zero++; swap(nums, i, zero); i++; } else if (nums[i] == 1) { i++; } else { swap(nums, i, two); two--; } } } private void swap(int[] nums, int index1, int index2) { int temp = nums[index1]; nums[index1] = nums[index2]; nums[index2] = temp; } }
[]from typing import List class Solution: def sortColors(self, nums: List[int]) -> None: """ Do not return anything, modify nums in-place instead. """ # all in [0, zero] = 0 # all in (zero, i) = 1 # all in (two, len - 1] = 2 def swap(nums, index1, index2): nums[index1], nums[index2] = nums[index2], nums[index1] size = len(nums) if size < 2: return zero = -1 two = size - 1 i = 0 while i <= two: if nums[i] == 0: zero += 1 swap(nums, i, zero) i += 1 elif nums[i] == 1: i += 1 else: swap(nums, i, two) two -= 1
[]#include <iostream> #include <vector> using namespace std; class Solution { public: void sortColors(vector<int> &nums) { int size = nums.size(); if (size < 2) { return; } // all in [0, zero] = 0 // all in (zero, i) = 1 // all in (two, len - 1] = 2 int zero = -1; int two = size - 1; int i = 0; while (i <= two) { if (nums[i] == 0) { zero++; swap(nums[zero], nums[i]); i++; } else if (nums[i] == 1) { i++; } else { swap(nums[i], nums[two]); two--; } } } };
复杂度分析:(同参考代码 1)
总结
「循环不变量」主要用于证明算法的正确性,在《算法导论》里大量使用了「循环不变量」这个工具。
- 第 2.1 节 插入排序
- 第 2.3.1 节 分治法
- 第 6.3 节 建堆
- 第 7.1 节 快速排序的描述
其实「循环不变量」并不是一个很高深的概念,其实我们很多时候,在编写代码的过程中都在不自觉地维护了变量的定义。「循环不变量」只是一个学术化的名字而已,设计清楚「循环不变量」,可以帮助我们写出正确的代码。
关于「循环不变量」,我做了一个专题的视频教程,讲了 4 个「力扣」上的问题,感兴趣的朋友可以在 B 站搜索「liweiwei1419」,专题三就是循环不变量,很好找。
统计信息
通过次数 | 提交次数 | AC比率 |
---|---|---|
294177 | 491423 | 59.9% |
提交历史
提交时间 | 提交结果 | 执行时间 | 内存消耗 | 语言 |
---|
相似题目
题目 | 难度 |
---|---|
排序链表 | 中等 |
摆动排序 | 中等 |
摆动排序 II | 中等 |