Top K问题

208 阅读2分钟

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;

}

www.cnblogs.com/xiaokang01/… www.cnblogs.com/woxiaosade/…