【算法题】快排

快速排序是基于分治思想 的经典算法,核心逻辑是"选基准→分区→递归处理子区间":通过将数组切分为"小于基准""等于基准""大于基准"的三个部分,逐步缩小排序范围,平均时间复杂度为 O(nlog⁡n)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]

解题思路:

这是快排三向分区思想的典型应用(荷兰国旗问题):

  1. 定义三个指针:
    • left0 的右边界(初始 -1,表示暂无非零元素);
    • right2 的左边界(初始 n,表示暂无2元素);
    • i:遍历数组的指针(初始 0)。
  2. 遍历过程:
    • 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(nlog⁡n)O(n\log n)O(nlogn))。

示例

  • 输入:nums = [5,2,3,1],输出:[1,2,3,5]

解题思路:

实现带随机基准+三向切分的快速排序(避免有序数组的最坏情况,同时高效处理重复元素):

  1. 随机选基准 :从当前区间随机选一个元素作为基准(避免有序数组下时间复杂度退化到 O(n2)O(n^2)O(n2))。
  2. 三向切分 :将数组分为"小于基准""等于基准""大于基准"的三个部分(用 left 标记小于区右边界,right 标记大于区左边界)。
  3. 递归处理:对"小于基准"和"大于基准"的区间递归排序(等于基准的部分已有序,无需处理)。

完整代码:

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(nlog⁡n)O(n\log n)O(nlogn),最坏 O(n2)O(n^2)O(n2)(随机选基准后概率极低)。
  • 空间复杂度:O(log⁡n)O(\log n)O(logn),递归栈的深度(平均情况)。

三、数组中的第K个最大元素

题目描述:

找到数组中第 K 个最大的元素(要求时间复杂度 O(n)O(n)O(n))。

示例

  • 输入:nums = [3,2,1,5,6,4], k = 2,输出:5

解题思路:

利用快排的快速选择算法(无需完全排序,仅需找到目标元素所在的分区):

  1. 随机选基准并三向切分数组,得到"小于基准""等于基准""大于基准"的三个区间。
  2. 计算大于基准的区间长度 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(log⁡n)O(\log n)O(logn),递归栈的深度。

四、库存管理III(取最小的cnt个元素)

题目描述:

给定库存数组 stock,返回库存最少的 cnt 个元素(顺序不限)。

示例

  • 输入:stock = [2,5,7,4], cnt = 1,输出:[2]

解题思路:

同样基于快速选择思想,只需找到"最小的cnt个元素"所在的分区:

  1. 随机选基准并三向切分数组,得到"小于基准""等于基准""大于基准"的三个区间。
  2. 计算小于基准的区间长度 a等于基准的区间长度 b
    • a > cnt:最小的cnt个元素在"小于基准"的区间,递归处理该区间;
    • a + b >= cnt:当前区间已包含最小的cnt个元素,无需继续递归;
    • 否则:最小的cnt个元素还包含"大于基准"区间的部分,递归处理该区间(调整cnt为 cnt - a - b)。
  3. 最终取数组前 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(log⁡n)O(\log n)O(logn),递归栈的深度。
相关推荐
多米Domi01116 小时前
0x3f 第48天 面向实习的八股背诵第五天 + 堆一题 背了JUC的题,java.util.Concurrency
开发语言·数据结构·python·算法·leetcode·面试
2301_8223776516 小时前
模板元编程调试方法
开发语言·c++·算法
故以往之不谏16 小时前
函数--值传递
开发语言·数据结构·c++·算法·学习方法
渐暖°16 小时前
【leetcode算法从入门到精通】5. 最长回文子串
vscode·算法·leetcode
今天_也很困16 小时前
LeetCode热题100-560. 和为 K 的子数组
java·算法·leetcode
v_for_van16 小时前
力扣刷题记录2(无算法背景,纯C语言)
c语言·算法·leetcode
2301_8112329816 小时前
低延迟系统C++优化
开发语言·c++·算法
alphaTao16 小时前
LeetCode 每日一题 2026/1/26-2026/2/1
算法·leetcode
Christo317 小时前
TFS-2026《Fuzzy Multi-Subspace Clustering 》
人工智能·算法·机器学习·数据挖掘
2401_8576835417 小时前
C++中的原型模式
开发语言·c++·算法