mirror of
https://github.com/krahets/LeetCode-Book.git
synced 2026-01-12 00:19:02 +08:00
9.7 KiB
Executable File
9.7 KiB
Executable File
方法一:快速排序
本题使用排序算法解决最直观,对数组 stock 执行排序,再返回前 cnt 个元素即可。使用任意排序算法皆可,本文采用并介绍 快速排序 ,为下文 方法二 做铺垫。
快速排序原理:
快速排序算法有两个核心点,分别为 “哨兵划分” 和 “递归” 。
哨兵划分操作: 以数组某个元素(一般选取首元素)为 基准数 ,将所有小于基准数的元素移动至其左边,大于基准数的元素移动至其右边。
如下图所示,为哨兵划分操作流程。通过一轮 哨兵划分 ,可将数组排序问题拆分为 两个较短数组的排序问题 (本文称之为左(右)子数组)。
递归: 对 左子数组 和 右子数组 递归执行 哨兵划分,直至子数组长度为 1 时终止递归,即可完成对整个数组的排序。
如下图所示,为示例数组
[2,4,1,0,3,5]的快速排序流程。观察发现,快速排序和 二分法 的原理类似,都是以\log时间复杂度实现搜索区间缩小。
代码:
class Solution:
def inventoryManagement(self, stock: List[int], cnt: int) -> List[int]:
def quick_sort(stock, l, r):
# 子数组长度为 1 时终止递归
if l >= r: return
# 哨兵划分操作(以 stock[l] 作为基准数)
i, j = l, r
while i < j:
while i < j and stock[j] >= stock[l]: j -= 1
while i < j and stock[i] <= stock[l]: i += 1
stock[i], stock[j] = stock[j], stock[i]
stock[l], stock[i] = stock[i], stock[l]
# 递归左(右)子数组执行哨兵划分
quick_sort(stock, l, i - 1)
quick_sort(stock, i + 1, r)
quick_sort(stock, 0, len(stock) - 1)
return stock[:cnt]
class Solution {
public int[] inventoryManagement(int[] stock, int cnt) {
quickSort(stock, 0, stock.length - 1);
return Arrays.copyOf(stock, cnt);
}
private void quickSort(int[] stock, int l, int r) {
// 子数组长度为 1 时终止递归
if (l >= r) return;
// 哨兵划分操作(以 stock[l] 作为基准数)
int i = l, j = r;
while (i < j) {
while (i < j && stock[j] >= stock[l]) j--;
while (i < j && stock[i] <= stock[l]) i++;
swap(stock, i, j);
}
swap(stock, i, l);
// 递归左(右)子数组执行哨兵划分
quickSort(stock, l, i - 1);
quickSort(stock, i + 1, r);
}
private void swap(int[] stock, int i, int j) {
int tmp = stock[i];
stock[i] = stock[j];
stock[j] = tmp;
}
}
class Solution {
public:
vector<int> inventoryManagement(vector<int>& stock, int cnt) {
quickSort(stock, 0, stock.size() - 1);
vector<int> res;
res.assign(stock.begin(), stock.begin() + cnt);
return res;
}
private:
void quickSort(vector<int>& stock, int l, int r) {
// 子数组长度为 1 时终止递归
if (l >= r) return;
// 哨兵划分操作(以 stock[l] 作为基准数)
int i = l, j = r;
while (i < j) {
while (i < j && stock[j] >= stock[l]) j--;
while (i < j && stock[i] <= stock[l]) i++;
swap(stock[i], stock[j]);
}
swap(stock[i], stock[l]);
// 递归左(右)子数组执行哨兵划分
quickSort(stock, l, i - 1);
quickSort(stock, i + 1, r);
}
};
复杂度分析:
- 时间复杂度
O(N \log N): 库函数、快排等排序算法的平均时间复杂度为O(N \log N)。 - 空间复杂度
O(N): 快速排序的递归深度最好(平均)为O(\log N),最差情况(即输入数组完全倒序)为 $O(N)$。
方法二:快速选择
题目只要求返回最小的 cnt 个数,对这 cnt 个数的顺序并没有要求。因此,只需要将数组划分为 最小的 cnt 个数 和 其他数字 两部分即可,而快速排序的哨兵划分可完成此目标。
根据快速排序原理,如果某次哨兵划分后 基准数正好是第 cnt+1 小的数字 ,那么此时基准数左边的所有数字便是题目所求的 最小的 cnt 个数 。
根据此思路,考虑在每次哨兵划分后,判断基准数在数组中的索引是否等于 cnt ,若 \text{true} 则直接返回此时数组的前 cnt 个数字即可。
算法流程:
inventoryManagement() 函数:
- 若
cnt大于数组长度,则直接返回整个数组; - 执行并返回
quick_sort()即可;
quick_sort() 函数:
注意,此时
quick_sort()的功能不是排序整个数组,而是搜索并返回最小的cnt个数。
- 哨兵划分:
- 划分完毕后,基准数为
stock[i],左 / 右子数组区间分别为[l, i - 1],[i + 1, r];
- 递归或返回:
- 若
cnt < i,代表第cnt + 1小的数字在 左子数组 中,则递归左子数组; - 若
cnt > i,代表第cnt + 1小的数字在 右子数组 中,则递归右子数组; - 若
cnt = i,代表此时stock[cnt]即为第cnt + 1小的数字,则直接返回数组前cnt个数字即可;
代码:
class Solution:
def inventoryManagement(self, stock: List[int], cnt: int) -> List[int]:
if cnt >= len(stock): return stock
def quick_sort(l, r):
i, j = l, r
while i < j:
while i < j and stock[j] >= stock[l]: j -= 1
while i < j and stock[i] <= stock[l]: i += 1
stock[i], stock[j] = stock[j], stock[i]
stock[l], stock[i] = stock[i], stock[l]
if cnt < i: return quick_sort(l, i - 1)
if cnt > i: return quick_sort(i + 1, r)
return stock[:cnt]
return quick_sort(0, len(stock) - 1)
class Solution {
public int[] inventoryManagement(int[] stock, int cnt) {
if (cnt >= stock.length) return stock;
return quickSort(stock, cnt, 0, stock.length - 1);
}
private int[] quickSort(int[] stock, int cnt, int l, int r) {
int i = l, j = r;
while (i < j) {
while (i < j && stock[j] >= stock[l]) j--;
while (i < j && stock[i] <= stock[l]) i++;
swap(stock, i, j);
}
swap(stock, i, l);
if (i > cnt) return quickSort(stock, cnt, l, i - 1);
if (i < cnt) return quickSort(stock, cnt, i + 1, r);
return Arrays.copyOf(stock, cnt);
}
private void swap(int[] stock, int i, int j) {
int tmp = stock[i];
stock[i] = stock[j];
stock[j] = tmp;
}
}
class Solution {
public:
vector<int> inventoryManagement(vector<int>& stock, int cnt) {
if (cnt >= stock.size()) return stock;
return quickSort(stock, cnt, 0, stock.size() - 1);
}
private:
vector<int> quickSort(vector<int>& stock, int cnt, int l, int r) {
int i = l, j = r;
while (i < j) {
while (i < j && stock[j] >= stock[l]) j--;
while (i < j && stock[i] <= stock[l]) i++;
swap(stock[i], stock[j]);
}
swap(stock[i], stock[l]);
if (i > cnt) return quickSort(stock, cnt, l, i - 1);
if (i < cnt) return quickSort(stock, cnt, i + 1, r);
vector<int> res;
res.assign(stock.begin(), stock.begin() + cnt);
return res;
}
};
复杂度分析:
本方法优化时间复杂度的本质是通过判断舍去了不必要的递归(哨兵划分)。
- 时间复杂度
O(N): 其中N为数组元素数量;对于长度为N的数组执行哨兵划分操作的时间复杂度为O(N);每轮哨兵划分后根据cnt和i的大小关系选择递归,由于i分布的随机性,则向下递归子数组的平均长度为\frac{N}{2};因此平均情况下,哨兵划分操作一共有N + \frac{N}{2} + \frac{N}{4} + ... + \frac{N}{N} = \frac{N - \frac{1}{2}}{1 - \frac{1}{2}} = 2N - 1(等比数列求和),即总体时间复杂度为O(N)。 - 空间复杂度
O(\log N): 划分函数的平均递归深度为O(\log N)。















