top K问题有两种,第K大(小)的值;前K大(小)的值。
前K大(小)的值。
1. 排序,如快排 O(NlogN)
排完序,自然可以找到第k个值,前面的就是top K个值。 (对前top K进行排序了,有点浪费)
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
quickSort(arr, 0, arr.size() - 1);
vector<int> res;
res.assign(arr.begin(), arr.begin() + k);
return res;
}
private:
void quickSort(vector<int>& arr, int l, int r) {
// 子数组长度为 1 时终止递归
if (l >= r) return;
// 哨兵划分操作(以 arr[l] 作为基准数)
int i = l, j = r;
while (i < j) {
while (i < j && arr[j] >= arr[l]) j--;
while (i < j && arr[i] <= arr[l]) i++;
swap(arr[i], arr[j]);
}
swap(arr[i], arr[l]);
// 递归左(右)子数组执行哨兵划分
quickSort(arr, l, i - 1);
quickSort(arr, i + 1, r);
}
};
2. 优化后的快排 O(N)
哨兵划分,左右子数组; 如果k<i, 此时第k+1小的数字在左边,则递归左子数组; 如果k>i, 此时第k+1小的数字在右边,则递归左子数组; 如果k==i, 此时i前面有k个数字,直接返回i前面的数组;
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
if(k>=arr.size())return arr;
return quickSort(arr, k, 0, arr.size()-1);
}
vector<int> quickSort(vector<int>& arr, int k, int l, int r){
int i=l, j = r;
while(i<j){
while(i<j&&arr[j]>=arr[l])j--; // 找到右边比基准小的值
while(i<j&&arr[i]<=arr[l])i++; // 找到右边比基准小的值
swap(arr[i],arr[j]);
}
swap(arr[i],arr[l]);
if(i>k) return quickSort(arr, k, l, i-1);
if(i<k) return quickSort(arr, k, i+1, r);
vector<int> res;
res.assign(arr.begin(), arr.begin()+k);
return res;
}
};
3. 堆 O(NlogK)
如果要找top K大的数据
1:首先选取前K个数建立小顶堆(根结点值大于左右结点值)。
(寻找最小的k个数,要用大顶堆)
(寻找最大的k个数,要用小顶堆)
2:此后,每次从原数组中取一个元素与根进行比较,如果小于根结点的元素,忽视之,取下一个数组元素继续该过程;如果大于根结点的元素,则将其加入小顶堆,并进行堆调整(和堆顶替换),将根元素移动到最后再删除,即保证小顶堆中的元素仍然是最大的前K的数,且根元素仍然最小。
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
vector<int> vec;
if(k<=0){
return {};
}
if(arr.size()<=k){
return arr;
}
for(int i=0; i<k; i++){
vec.push_back(arr[i]);
}
make_heap(vec.begin(), vec.end(), less<int>()); // 大顶堆
for(int i=k; i<arr.size(); i++){
if(arr[i]>vec.front()){ // 跳过
continue;
}else{
vec.push_back(arr[i]);
// 添加新元素调整堆
push_heap(vec.begin(), vec.end());
// 将堆顶元素调整到最后
pop_heap(vec.begin(), vec.end());
vec.pop_back(); // 删除最后的元素
}
}
return vec;
}
};
4. 手写堆排序
class Solution {
public:
void adjust(vector<int>& vec, int len, int index){
if(index>len)return; // 递归出口
int left = 2*index+1; // index的左孩子
int right = 2*index+2; // index的右孩子
int maxIdx = index;
// 将maxIdx作为子树的最大值,在下标不越界的情况下
if (left < len && vec[maxIdx] < vec[left] ) {
maxIdx = left;
}
if (right < len && vec[maxIdx] < vec[right]) {
maxIdx = right;
}
// 最大值不在堆顶,就交换
if(index!=maxIdx){
swap(vec[index], vec[maxIdx]);
adjust(vec, len, maxIdx); // 递归所有子树
}
}
vector<int> getLeastNumbers(vector<int>& arr, int k) {
int len = arr.size();
if(k<=0){
return {};
}
if(len<=k){
return arr;
}
vector<int>max_heap;
for(int i=0; i<k; i++){
max_heap.push_back(arr[i]);
}
// 对k个元素构建小根堆,循环结束得到一个含有K个元素的大根堆
for(int i=k/2-1; i>=0; i--){
adjust(max_heap, k, i);
}
// 对input容器中剩余元素和堆顶比较
for(int i=k; i<len; i++){
if(arr[i]<max_heap[0]){
max_heap[0] = arr[i];
adjust(max_heap, k, 0); // 对堆顶元素进行调整
}
}
return max_heap;
}
};
第K大(小)的值。
基于快排的选择方法
建堆,再删除
#include <iostream>
#include<vector>
#include<set>
using namespace std;
void adjust(vector<int>& vec, int len, int index) {
if (index > len)return; // 递归出口
int left = 2 * index + 1; // index的左孩子
int right = 2 * index + 2; // index的右孩子
int maxIdx = index;
// 将maxIdx作为子树的最大值,在下标不越界的情况下
if (left < len && vec[maxIdx] < vec[left] ) {
maxIdx = left;
}
if (right < len && vec[maxIdx] < vec[right]) {
maxIdx = right;
}
// 最大值不在堆顶,就交换
if (index != maxIdx) {
swap(vec[index], vec[maxIdx]);
adjust(vec, len, maxIdx); // 递归所有子树
}
}
void build_heap(vector<int>& arr, int len) {
for (int i = len / 2 - 1; i >= 0; i--) {
adjust(arr, len, i);
}
for (int num : arr) {
cout << num << " ";
}
cout << endl;
}
int findKthLargest(vector<int>& nums, int k) {
int heapSize = nums.size();
build_heap(nums, heapSize);
// 8-1 8-4+1 5 删掉k-1个
for (int i = nums.size() - 1; i >= nums.size() - k + 1; --i) {
swap(nums[0], nums[i]);
--heapSize;
adjust(nums, heapSize, 0);
for (int num : nums) {
cout << num << " ";
}
cout << endl;
}
return nums[0];
}
int main() {
vector<int> data = {4, 5, 6, 0, 9, 3, 2, 1};
int ans = findKthLargest(data, 4);
cout << ans << endl;
cout << endl;
}