文章目录
1. 题目来源
链接:最小的k个数
来源:LeetCode——《剑指-Offer》专项
2. 题目说明
输入整数数组 arr
,找出其中最小的 k
个数。例如,输入 4、5、1、6、2、7、3、8 这 8 个数字,则最小的 4 个数字是 1、2、3、4。
示例 1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例 2:
输入:arr = [0,1,2,1], k = 1
输出:[0]
限制:
0 <= k <= arr.length <= 10000
0 <= arr[i] <= 10000
3. 题目解析
方法一:快排思想+ O ( n ) O(n) O(n)时间复杂度算法+巧妙解法
其实在 [剑指-Offer] 39. 数组中出现次数超过一半的数字(多数投票算法、sort函数、代码优化)
中就可以采用快速排序中基于 partition
函数的思想来解决该问题,在原书中该方法也是大力被倡导。故在本问题中进行实现。基于数组的第 k
个数字来调整,则使得比第 k
个数字小的所有数字都位于数组的左边,比第 k
个数字打的数字都位于数组的右边。这样调整过后,位于数组中左边的 k
个数字就是最小的 k
个数字,但是这 k
不是排序的。
使用三数去中法来确定快排的基准值,双指针遍历法来进行区间划分。详情请见博主的排序算法系列博文:[排序算法] 6. 快速排序多种递归、非递归实现及性能对比(交换排序)
参见代码如下:
// 执行用时 :32 ms, 在所有 C++ 提交中击败了86.92%的用户
// 内存消耗 :21.2 MB, 在所有 C++ 提交中击败了100.00%的用户
class Solution {
public:
int getMidIndex(vector<int> &array, int start, int end) {
int mid = start + ((end - start) >> 1);
int a = array[start];
int b = array[mid];
int c = array[end];
if ((a >= b && a <= c) || (a <= b && a >= c)) return start;
if ((b >= a && b <= c) || (b <= a && b >= c)) return mid;
if ((c >= a && c <= b) || (c <= a && c >= b)) return end;
return -1;
}
int partition(vector<int> &array, int start, int end) {
int midIndex = getMidIndex(array, start, end);
int temp = array[midIndex];
array[midIndex] = array[end];
array[end] = temp;
temp = array[end];
while (start < end) {
while (start < end && array[start] < temp) start++;
if (start < end) {
array[end] = array[start];
end--;
}
while (start < end && array[end] >= temp) end--;
if (start < end) {
array[start] = array[end];
start++;
}
}
array[start] = temp;
return start;
}
vector<int> getLeastNumbers(vector<int>& nums, int k) {
if (k == 0) return {};
int start = 0;
int end = (int)nums.size() - 1;
int position = partition(nums, start, end);
int index = k - 1;
while (index != position) {
if (index > position) start = position + 1;
else end = position - 1;
position = partition(nums, start, end);
}
vector<int>rs(position + 1);
for (int i = 0; i <= position; i++) rs[i] = nums[i];
return rs;
}
};
方法二: O ( l o g n ) O(logn) O(logn)时间复杂度+海量数据+巧妙解法
Top k
问题还是很适合堆排序的,并且采用堆排序也能满足海量数据的要求的。在泛化一下思想,其实堆这种数据结构还是来源于二叉树,并且 RBT
能够满足我们的要求,查删插都只用
O
(
l
o
g
n
)
O(logn)
O(logn) 的时间复杂度。在 STL
中 set
和 multiset
都是基于红黑树实现的。可以采用 multiset
进行实现。只要这个 k
不大于内存空间,那么就能够切分的进行海量数据的比较得出答案。
下面代码实现的也是比较详细的,没有采用 auto
关键字,尽量每一步的清楚完整,尤其注意下 set
排序传参问题,以及自定义排序问题即可。
采用该解法虽然时间复杂度不如方法一,但好处是不会对原数组进行修改,而采用方法一的快排思想,会对原数组进行修改。在编写程序前应该仔细询问数据大小是否能一次载入内存、数据允许被修改吗?切记不要一上来就撸代码,没啥意义,会留下不仔细考虑问题的坏印象。
参见代码如下:
// 执行用时 :56 ms, 在所有 C++ 提交中击败了40.30%的用户
// 内存消耗 :26.9 MB, 在所有 C++ 提交中击败了100.00%的用户
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
vector<int> vt;
multiset<int, greater<int>> s;
multiset<int, greater<int>>::iterator sit;
vector<int>::iterator vit = arr.begin();
for (; vit != arr.end(); ++vit) {
if (s.size() < k) s.insert(*vit);
else {
sit = s.begin();
if (*vit < *(s.begin())) {
s.erase(sit);
s.insert(*vit);
}
}
}
for (auto it = s.begin(); it != s.end(); ++it) vt.emplace_back(*it);
return vt;
}
};