剑指offer29_最小的K个数_题解
最小的K个数
题目描述
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。
示例1
输入
[4,5,1,6,2,7,3,8],4
返回值
[1,2,3,4]
分析
方案一:堆排序
用一个大根堆实时维护数组的前 \(k\) 小值。
代码
/**
时间复杂度:O(nlongk),
其中 n 是数组 arr 的长度。由于大根堆实时维护前 k 小值,所以插入删除都是 O(log k) 的时间复杂度,最坏情况下数组里 n 个数都会插入,所以一共需要 O(nlogk) 的时间复杂度。
空间复杂度:O(k)
**/
class Solution
{
public:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k)
{
vector<int> res;
if (k > input.size())
return res;
priority_queue<int, vector<int>, greater<int>> pq(input.begin(), input.end());
while (k--)
{
res.push_back(pq.top());
pq.pop();
}
return res;
}
};
方案二:快速选择
借鉴快速排序的思想,当对数组执行一次 \(Partition\) 函数之后,主元 \(A[p]\) 左侧的元素均小于主元。假设此时主元是 \(A[p]\) 那么 \(A[p]\) 就是 \(A[left, right]\) 中的第 \(p - left + 1\) 大的数。令 \(M\) 表示 \(p - left + 1\)
-
\(K = M\) 第 \(K\) 大的数就是主元 \(A[p]\)
-
\(K < M\) 第 \(K\) 大的数在主元左侧,即 \(A[left...(p-1)]\) 中的第 \(K\) 大,往左侧递归
-
\(K > M\) 第 \(K\) 大的数在主元右侧,即 \(A[p+1...right]\) 中的第 \(K\) 大,往右侧递归
代码
/**
平均时间复杂度:O(n)
最坏时间复杂度:O(n^2)
空间复杂度:O(1)
**/
class Solution2
{
public:
int Partition(vector<int> &input, int left, int right)
{
int pivot = input[left];
while (left < right)
{
//从右到左找到第一个比pivot小的元素
while (left < right && input[right] >= pivot)
--right;
swap(input[left], input[right]);
//从左到右找到第一个比pivot大的元素
while (left < right && input[left] <= pivot)
++left;
swap(input[left], input[right]);
}
input[left] = pivot;
return left;
}
int QuickSelect(vector<int> input, int left, int right, int k)
{
if (left == right)
return input[left];
int p = Partition(input, left, right);
int M = p - left + 1;
if (M == k)
{
return input[p];
}
else if (M > k)
{
return QuickSelect(input, left, p - 1, k);
}
else if (M < k)
{
return QuickSelect(input, p + 1, right, k - M);
}
return input[left];
}
vector<int> GetLeastNumbers_Solution(vector<int> input, int k)
{
vector<int> res;
int n = input.size();
if (n == 0 || k > n)
return res;
for (int i = 1; i <= k; i++)
{
int val = QuickSelect(input, 0, n - 1, i);
res.push_back(val);
}
return res;
}
};