快速排序是基于分治思想 的经典算法,核心逻辑是"选基准→分区→递归处理子区间":通过将数组切分为"小于基准""等于基准""大于基准"的三个部分,逐步缩小排序范围,平均时间复杂度为 O(nlogn)O(n\log n)O(nlogn)。除了基础排序,快排的"分区+选择"思想还能高效解决"找第K大元素""取最小的N个元素"等问题。本文通过4道经典题目,拆解快排思想在不同场景下的应用。
一、颜色分类
题目描述:
给定含 0、1、2(对应红、白、蓝)的数组,原地排序使相同颜色相邻,且按 0→1→2 顺序排列(不能用内置排序函数)。
示例:
- 输入:
nums = [2,0,2,1,1,0],输出:[0,0,1,1,2,2]
解题思路:
这是快排三向分区思想的典型应用(荷兰国旗问题):
- 定义三个指针:
left:0的右边界(初始-1,表示暂无非零元素);right:2的左边界(初始n,表示暂无2元素);i:遍历数组的指针(初始0)。
- 遍历过程:
- 若
nums[i] == 0:与left+1交换,left++并i++(0的区域扩大); - 若
nums[i] == 2:与right-1交换,right--(2的区域扩大,i不递增,需重新检查交换后的元素); - 若
nums[i] == 1:直接i++(1的区域自然保留)。
- 若
完整代码:
cpp
class Solution {
public:
void sortColors(vector<int>& nums) {
int n = nums.size();
int left = -1, right = n, i = 0;
while(i < right)
{
if(nums[i] == 0)
swap(nums[++left], nums[i++]);
else if(nums[i] == 1)
i++;
else
swap(nums[--right], nums[i]);
}
}
};
复杂度分析:
- 时间复杂度:O(n)O(n)O(n),仅遍历数组一次。
- 空间复杂度:O(1)O(1)O(1),原地修改数组。
二、排序数组
题目描述:
实现快速排序,将整数数组升序排列(不能用内置排序函数,要求时间复杂度 O(nlogn)O(n\log n)O(nlogn))。
示例:
- 输入:
nums = [5,2,3,1],输出:[1,2,3,5]
解题思路:
实现带随机基准+三向切分的快速排序(避免有序数组的最坏情况,同时高效处理重复元素):
- 随机选基准 :从当前区间随机选一个元素作为基准(避免有序数组下时间复杂度退化到 O(n2)O(n^2)O(n2))。
- 三向切分 :将数组分为"小于基准""等于基准""大于基准"的三个部分(用
left标记小于区右边界,right标记大于区左边界)。 - 递归处理:对"小于基准"和"大于基准"的区间递归排序(等于基准的部分已有序,无需处理)。
完整代码:
cpp
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
srand(time(NULL));
qsort(nums, 0, nums.size() - 1);
return nums;
}
void qsort(vector<int>& nums, int l, int r) {
if(l >= r) return;
int key = getRand(nums, l, r); // 随机选基准
int i = l, left = l - 1, right = r + 1;
// 三向切分
while(i < right) {
if(nums[i] < key) swap(nums[++left], nums[i++]);
else if(nums[i] == key) i++;
else swap(nums[--right], nums[i]);
}
qsort(nums, l, left); // 排序小于基准的区间
qsort(nums, right, r); // 排序大于基准的区间
}
int getRand(vector<int>& nums, int left, int right) {
int r = rand();
return nums[left + r % (right - left + 1)];
}
};
复杂度分析:
- 时间复杂度:平均 O(nlogn)O(n\log n)O(nlogn),最坏 O(n2)O(n^2)O(n2)(随机选基准后概率极低)。
- 空间复杂度:O(logn)O(\log n)O(logn),递归栈的深度(平均情况)。
三、数组中的第K个最大元素
题目描述:
找到数组中第 K 个最大的元素(要求时间复杂度 O(n)O(n)O(n))。
示例:
- 输入:
nums = [3,2,1,5,6,4], k = 2,输出:5
解题思路:
利用快排的快速选择算法(无需完全排序,仅需找到目标元素所在的分区):
- 随机选基准并三向切分数组,得到"小于基准""等于基准""大于基准"的三个区间。
- 计算大于基准的区间长度
c和 等于基准的区间长度b:- 若
c >= k:第K大元素在"大于基准"的区间,递归处理该区间; - 若
b + c >= k:第K大元素就是基准(等于基准的区间包含目标); - 否则:第K大元素在"小于基准"的区间,递归处理该区间(需调整K为
k - b - c)。
- 若
完整代码:
cpp
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
srand(time(NULL));
return qsort(nums, 0, nums.size() - 1, k);
}
int qsort(vector<int>& nums, int l, int r, int k) {
if(l == r) return nums[l];
int key = getRand(nums, l, r);
int i = l, left = l - 1, right = r + 1;
// 三向切分
while(i < right) {
if(nums[i] < key) swap(nums[++left], nums[i++]);
else if(nums[i] == key) i++;
else swap(nums[--right], nums[i]);
}
int c = r - right + 1; // 大于基准的元素个数
int b = right - 1 - (left + 1) + 1; // 等于基准的元素个数
if(c >= k) return qsort(nums, right, r, k);
else if(b + c >= k) return key;
else return qsort(nums, l, left, k - b - c);
}
int getRand(vector<int>& nums, int l, int r) {
return nums[l + rand() % (r - l + 1)];
}
};
复杂度分析:
- 时间复杂度:平均 O(n)O(n)O(n),最坏 O(n2)O(n^2)O(n2)(随机选基准后概率极低)。
- 空间复杂度:O(logn)O(\log n)O(logn),递归栈的深度。
四、库存管理III(取最小的cnt个元素)
题目描述:
给定库存数组 stock,返回库存最少的 cnt 个元素(顺序不限)。
示例:
- 输入:
stock = [2,5,7,4], cnt = 1,输出:[2]
解题思路:
同样基于快速选择思想,只需找到"最小的cnt个元素"所在的分区:
- 随机选基准并三向切分数组,得到"小于基准""等于基准""大于基准"的三个区间。
- 计算小于基准的区间长度
a和 等于基准的区间长度b:- 若
a > cnt:最小的cnt个元素在"小于基准"的区间,递归处理该区间; - 若
a + b >= cnt:当前区间已包含最小的cnt个元素,无需继续递归; - 否则:最小的cnt个元素还包含"大于基准"区间的部分,递归处理该区间(调整cnt为
cnt - a - b)。
- 若
- 最终取数组前
cnt个元素即可。
完整代码:
cpp
class Solution {
public:
vector<int> inventoryManagement(vector<int>& stock, int cnt) {
srand(time(NULL));
qsort(stock, 0, stock.size() - 1, cnt);
return {stock.begin(), stock.begin() + cnt};
}
void qsort(vector<int>& nums, int l, int r, int k) {
if(l >= r) return;
int key = getRand(nums, l, r);
int i = l, left = l - 1, right = r + 1;
// 三向切分
while(i < right) {
if(nums[i] < key) swap(nums[++left], nums[i++]);
else if(nums[i] == key) i++;
else swap(nums[--right], nums[i]);
}
int a = left - l + 1; // 小于基准的元素个数
int b = right - left - 1; // 等于基准的元素个数
if(a > k) qsort(nums, l, left, k);
else if(a + b >= k) return;
else qsort(nums, right, r, k - a - b);
}
int getRand(vector<int>& nums, int l, int r) {
return nums[l + rand() % (r - l + 1)];
}
};
复杂度分析:
- 时间复杂度:平均 O(n)O(n)O(n),最坏 O(n2)O(n^2)O(n2)。
- 空间复杂度:O(logn)O(\log n)O(logn),递归栈的深度。