本文正在参与掘金团队号上线活动,点击 查看大厂春招职位
一、题目描述:
今天要写的题是一道非常经典的算法题,也是面试中常见的一道题,解决这道题背后的思想也十分重要,它就是 LeetCode 面试题 17.14. 最小K个数。
二、思路分析:
这道题最朴素的做法是直接排序,在数据量不大的时候或者平时我们当然可以直接这样写,快速排序的复杂度也不过是 ,可以接受。但如果是在面试的时候碰到这道题,那可就得好好想一想这样做是否合适了。
注意到这道题只要找到最小的 k 个数,所以应当会有一种方法不用将整个数组排序就找到这 k 个数。特别的,如果这个 k 就等于数组的大小,那我们就不需要排序直接返回即可。如果你学过快速排序那么就应该能很容易地联想到快速排序的每一次“划分”都会将数组分成两个部分,左边的部分小于等于选定值,右边的部分大于选定值,这个时候整个数组还不是有序的,但是假如左边部分的长度等于 k,那就刚好能够满足我们的要求了。基于此,我们只要再分类讨论一下很容易就能写出 AC 代码。
假设当前要划分的数组部分是 ,经过划分后 都有 , 都有 ,令 ,也就是满足 的区间长度。
-
如果 ,因为无需前 k 个有序,我们直接返回即可。
-
如果 ,那么我们就只需要再划分 即可。
-
如果 ,那么我们就划分 ,并且以前 k - cnt 个为目标即可。
三、AC 代码:
class Solution {
public:
vector<int> smallestK(vector<int>& arr, int k) {
sortK(arr, 0, arr.size() - 1, k);
return vector<int>(arr.begin(), arr.begin() + k);
}
void sortK(vector<int> &arr, int l, int r, int k) {
// 记得特判不需要排序的情况,如果没有这个的话遇到空数组就会出错
if (l >= r) {
return;
}
int val = arr[r];
int i = l - 1;
// 开始的时候,[l, i] <= val
for (int j = l; j < r; ++j) {
// 保持 [l, i] <= val,并且使 (i, j] > val
if (arr[j] <= val) {
swap(arr[++i], arr[j]);
}
}
// 到最后 j = r - 1, [l, i] <= val, (i, r - 1] > val, [r] = val;
swap(arr[++i], arr[r]); // 使得 [l, i - 1] <= val, [i] = val, (i, r] > val
// 因为我们要找的是最小的 k 个,所以只需要比较前面的 <= val 的区间的长度和 k
int cnt = i - l + 1;
if (k == cnt || k == cnt - 1) {
return;
}
if (k < cnt) {
sortK(arr, l, i - 1, k);
} else {
sortK(arr, i + 1, r, k - cnt);
}
}
};
四、总结:
快速排序作为经典的分治算法直到今天仍未过时,而且其中所蕴含的思想还能够运用在其他的问题上,这更加提醒了我们应该好好学习算法,并且归纳总结,毕竟比起具体的算法,其背后的思想才是解决办法的钥匙。