【leetcode】215. 数组中的第K个最大元素

题目内容

215. 数组中的第K个最大元素

思路分析

【方法一】参考资料--快速排序亲兄弟:快速选择算法

【方法一】力扣官方题解

方法一:快速选择

本题是快速选择的经典使用场景,就是在学习快速选择的过程中来顺便学习此题。

关于快速选择的学习笔记详见:快速选择算法

此外,快速选择也可以通过C++的nth_element函数来实现,这个老哥就直接用了nth_element。

方法二:基于堆排序的选择

本题还可对数组建堆,然后移除K-1次堆顶后堆顶的元素即为第K个最大元素。

关于二叉堆的学习笔记详见:二叉堆学习

代码

#include <vector>
#include <math.h>
#include <limits.h>

using namespace std;

// 快速选择
// 时间复杂度O(n)
// 空间复杂度O(logn),递归调用
class Solution1 {
public:
    void quick_find(vector<int>& nums, int k, int left, int right) {
       if (left > right) return;

       int pivot = nums[left];
       int i = left, j = right;

       while (i != j) {
          while (nums[j] > pivot && i < j) {
             --j;
          }
          while (nums[i] <= pivot && i < j) {
             ++i;
          }
          swap(nums[i], nums[j]);
       }

       nums[left] = nums[i];
       nums[i] = pivot;

       if (i > k - 1) {
          quick_find(nums, k, left, i-1);
       } else if (i < k - 1) {
          quick_find(nums, k, i+1, right);
       }
    }

    // 全程需注意第K大的数下标是K-1
    int findKthLargest(vector<int>& nums, int k) {
        int n = nums.size();
        
        // 转化:找第k个最大的数即找第n - k + 1大的数
        k = n - k + 1;
        
        quick_find(nums, k, 0, n-1);
        
        return nums[k - 1];
    }
};

// 二叉堆
// 时间复杂度O(nlogn),建堆O(n),移除堆顶O(klogn)
// 空间复杂度O(logn),递归代价
class Solution {
public:
    // 维护堆的性质
    // heap为需要维护的堆
    // i为需要调整的节点
    // heap_size为堆的大小
    void maxHeapify(vector<int>& heap, int i, int heap_size) {
       int left = 2 * i + 1;
       int right = 2 * i + 2;
       int max = i;

       if (left < heap_size && heap[left] > heap[max]) {
          max = left;
       }
       if (right < heap_size && heap[right] > heap[max]) {
          max = right;
       }
       if (max != i) {
          swap(heap[i], heap[max]);
          maxHeapify(heap, max, heap_size); // 继续调整交换后的节点
       }
       // 隐含停止条件:i为叶子节点
    }

    void buildHeap(vector<int>& nums) {
       // 叶子节点不用调整,因为没有孩子,所以调整也没用
       // 从底到上进行调整
       for (int i = nums.size()/2; i >= 0; --i) {
          maxHeapify(nums, i, nums.size());
       }
    }

    int findKthLargest(vector<int>& nums, int k) {
       buildHeap(nums);

       int n = nums.size();
       int heap_size = nums.size();

       // 倒数第K大需要删除K-1次根节点
       for (int i = 0; i < k - 1; ++i) {
          swap(nums[n - i - 1], nums[0]);
          // 将堆的大小减1,这里只是假的减去
          --heap_size; 
          // 从根进行调整
          maxHeapify(nums, 0, heap_size);
       }

       return nums[0];
    }
};

int main() {
   Solution s;
   int k = 2;
   vector<int> input{ 3,2,1,5,6,4 };
   int result = s.findKthLargest(input, k);
   int a = 0;
}

收获

学了一道比较经典的题目,快速选择和堆排序都很实用。