算法 - 快速排序 (Quick Sort)
1. 题目
给定一个整数数组 nums,把它按升序排序。要求手写快速排序,并分析平均与最坏时间复杂度、空间复杂度。
示例:
1 | 输入: [5, 2, 9, 1, 5, 6] |
2. 解题思路
快速排序基于分治(Divide & Conquer):
- 选基准 pivot:从数组里挑一个值(首/尾/随机/三数取中)。
- 分区 partition:重排数组,使「小于 pivot 的在左边、大于的在右边」,并把 pivot 放到它最终应在的位置
p。 - 递归:对左右两个子区间分别快排。
分区是灵魂。最常用 Lomuto 方案:以最后一个元素为 pivot,用指针 i 维护「小于区」的右边界,遍历指针 j,遇到比 pivot 小的就与 i+1 交换。
复杂度:
- 平均时间
O(n log n),每次大致对半分。 - 最坏时间
O(n²):数组已有序且总选端点为 pivot,退化为链式划分。随机化 pivot 可高概率避免。 - 空间
O(log n):递归栈(原地版本),非原地拼接版本为O(n)。 - 快排是不稳定排序(相等元素相对顺序可能改变)。
3. TypeScript 实现
3.1 简洁版(易记,额外空间 O(n))
1 | function quickSortSimple(nums: number[]): number[] { |
用
mid单独收集等于 pivot 的元素,能正确处理重复值且不重复比较,避免部分实现卡在相等上。
3.2 原地版(面试官更想看的)
1 | function quickSort(nums: number[], lo = 0, hi = nums.length - 1): number[] { |
4. 面试延伸
- 为什么快排实践中比归并快? 原地、缓存友好、常数小;归并需要额外数组。
- ** nth element / Top-K(快速选择 215 题)**:partition 后只递归一边,平均
O(n)找第 K 大,是快排的孪生应用。 - 三路快排(Dutch National Flag):把区间分成
< == >三段,专门治「大量重复元素」,此时等于段直接不再递归。 - 手写必问:pivot 怎么选?最坏什么情况?是否稳定?——三点答上来就是满分。
难度:中等 | 手写排序基本功 | 快速选择衍生高频