7.2 分治-快排:LeetCode 912. 排序数组
1. 题目链接
LeetCode 912. 排序数组
2. 题目描述
给定一个整数数组 nums
,请将其按升序排列。要求:
- 时间复杂度为 O(n log n)
- 空间复杂度为 O(log n)(递归栈空间)
- 必须原地修改数组,不能使用额外空间
示例:
- 输入:
nums = [5,2,3,1]
→ 输出:[1,2,3,5]
- 输入:
nums = [5,1,1,2,0,0]
→ 输出:[0,0,1,1,2,5]
3. 示例分析
- 常规案例:
- 输入:
[3,1,4,2]
→ 三路快排过程:- 随机选枢轴(如
2
),划分为[1,2,3,4]
- 递归处理左右子数组
- 随机选枢轴(如
- 输出:
[1,2,3,4]
- 输入:
- 重复元素案例:
- 输入:
[2,2,1,1,3]
→ 三路快排将1
和2
聚集到中间区,减少递归次数 - 输出:
[1,1,2,2,3]
- 输入:
- 边界案例:
- 输入:
[]
→ 输出:[]
- 输入:
[0]
→ 输出:[0]
- 输入:
4. 算法思路
核心思想:三路快速排序 + 随机化策略
- 三路划分:
- 将数组分为三部分:小于枢轴、等于枢轴、大于枢轴
- 对“小于”和“大于”部分递归排序,跳过“等于”部分
- 随机化枢轴:
- 随机选择枢轴值,避免最坏时间复杂度(如数组已有序)
- 递归终止:
- 子数组长度为 0 或 1 时停止递归
5. 边界条件与注意事项
- 空数组处理:
- 直接返回,无需操作
- 递归深度优化:
- 虽然理论上空间复杂度为 O(log n),但最坏情况下递归深度为 O(n),需注意栈溢出风险
- 随机数生成:
srand(time(NULL))
初始化随机种子,但若在极短时间内多次调用函数可能导致种子重复
- 重复元素优化:
- 三路划分法在处理重复元素时效率显著优于传统快排
6. 代码实现
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
srand(time(NULL)); // 初始化随机种子
qsort(nums, 0, nums.size()-1);
return nums;
}
private:
// 三路快排核心函数
void qsort(vector<int>& nums, int a, int b) {
if (a >= b) return; // 递归终止条件
int pivot = getRandomValue(nums, a, b); // 随机选择枢轴值
int i = a, left = a-1, right = b+1; // 三指针初始化
// 三路划分过程
while (i < right) {
if (nums[i] < pivot) {
swap(nums[++left], nums[i++]); // 小于区右扩
} else if (nums[i] == pivot) {
i++; // 等于区不动
} else {
swap(nums[--right], nums[i]); // 大于区左扩
}
}
// 递归处理子区间
qsort(nums, a, left); // 处理小于区
qsort(nums, right, b); // 处理大于区
}
// 随机选择枢轴值
int getRandomValue(vector<int>& nums, int a, int b) {
int r = rand(); // 生成随机数
return nums[a + r % (b - a + 1)]; // 区间内随机选取
}
};
关键代码解析
-
三指针初始化:
int i = a, left = a-1, right = b+1;
i
:当前处理指针left
:小于区右边界(初始为空)right
:大于区左边界(初始为空)
-
三路划分过程:
- 元素 < 枢轴:交换到小于区,
left
和i
右移 - 元素 == 枢轴:跳过处理,
i
右移 - 元素 > 枢轴:交换到大于区,
right
左移
- 元素 < 枢轴:交换到小于区,
-
递归范围确定:
- 最终区间划分:
[a, left]
(小于区)、[left+1, right-1]
(等于区)、[right, b]
(大于区) - 仅需递归处理小于区和大于区
- 最终区间划分:
与其他排序算法对比
算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 适用场景 |
---|---|---|---|---|
三路快排 | O(n log n) | O(n²) | O(log n) | 含重复元素的大数据集 |
归并排序 | O(n log n) | O(n log n) | O(n) | 需要稳定排序的场景 |
堆排序 | O(n log n) | O(n log n) | O(1) | 内存受限环境 |
插入排序 | O(n²) | O(n²) | O(1) | 小规模或基本有序数据 |
总结
三路快速排序通过随机化枢轴和三分区策略,在保证 O(n log n) 平均时间复杂度的同时,显著提升了对重复元素数据的处理效率。其核心优势体现在:
- 原地排序:无需额外空间,满足题目要求
- 重复元素优化:将等于枢轴的元素集中处理,减少递归次数
- 随机化策略:避免最坏情况时间复杂度
优化建议:
- 当递归深度较大时可切换为插入排序(如子数组长度 < 15)
- 使用洗牌算法预处理数组,进一步增强随机性
关键点:
- 理解三分区过程与指针移动规则
- 掌握随机化策略对算法鲁棒性的提升机制
附:三路划分过程示意图
初始数组:[3,1,4,2,5,2,2,6] (随机选择枢轴 2)
划分过程:
1. 小于区收集 1 → [1,3,4,2,5,2,2,6]
2. 等于区跳过 2 → [1,2,4,3,5,2,2,6]
3. 大于区收集 4,5,6 → [1,2,2,2,3,4,5,6]
最终区间:
小于区:[1], 等于区:[2,2,2], 大于区:[3,4,5,6]