算法通关村——解析堆在找 第K大的元素中 的应用

本节我们来讲讲LeetCode215. 在数组中找第K大的元素 题目,本节我们只讲解如何使用堆来解决本类题目,我们先看看题干
在这里插入图片描述
这个题是一道非常重要的题,主要解决方法有三个,选择法,堆查找法和快速排序法。
选择法很简单,就是先遍历一遍找到最大的元素,然后再遍历一遍找第二大的,然后再遍历一遍找第三大的,直到第K次就找到了目标值了。但是这种方法只适合在面试的时候预热,面试官不会让你这么简单就开始写代码,因为该方法的时间复杂度为O(NK),在leetCode中提交一定会出现 超时。
所以本题目还是使用 快速排序 和 堆查找比较妥当。

方法一:快速排序:

这里我就简单过一下快速排序的代码:

public void quicksort(int[] nums, int left, int right) {
    // 如果左边界大于等于右边界,说明数组已经有序,直接返回
    if (left >= right)
        return;
    // 定义左右边界的索引
    int start = left, end = right;
    // 选择基准值,这里选择中间位置的元素作为基准值
    int pivot = nums[(start + end) / 2];
    // 当左边界小于右边界时,进行循环
    while (left < right) {
        // 从左向右找到第一个小于基准值的元素
        while (left <= right && nums[left] > pivot) {
            left++;
        }
        // 从右向左找到第一个大于基准值的元素
        while (left <= right && nums[right] < pivot) {
            right--;
        }
        // 如果左边界小于等于右边界,交换两个元素的位置
        if (left <= right) {
            int tmp = nums[left];
            nums[left] = nums[right];
            nums[right] = tmp;
            left++;
            right--;
        }
    }
    // 对左半部分进行快速排序
    quicksort(nums, start, right);
    // 对右半部分进行快速排序
    quicksort(nums, left, end);
}

方法二:堆查找法

这个题其实用大堆小堆都可以解决的,但是我们推荐“找最大用小堆,找最小用大堆,找中间用两个堆”,这样更容易理解,适用范围也更广。我们构造一个大小只有4的小根堆,为了更好说明情况,我们扩展-下序列[3,2,3,1,24,5,1,5,6,2,3]。

堆满了之后,对于小根堆,并一定所有新来的元素都可以入堆的,只有大于根元素的才可以插入到堆中,否则就直接抛弃。这是一个很重要的前提。

另外元素进入的时候,先替换根元素,如果发现左右两个子树都小该怎么办呢?很显然应该与更小的那个比较,这样才能保证根元素一定是当前堆最小的。假如两个子孩子的值一样呢?那就随便选一个。
在这里插入图片描述
新元素插入的时候只是替换根元素,然后重新构造成小堆,完成,之后,你会神奇的发现此时根的根元素正好是第4大的元素。

这时候你会发现,不管要处理的序列有多大,或者是不是固定的,根元素每次都恰好是当前序列下的第K大元素。上面的图收篇幅所限,我们省略了部分调整环节,请读者自行画一下看看。

上代码自己实现是非常困难的,我们可以使用jdk的优先队列来解决,其思路是很简单的。由于找第K大元素,其实就是整个数组排序以后后半部分最小的那个元素。因此,我们可以维护一个有K个元素的最小堆:

  • 如果当前堆不满,直接添加;
  • 堆满的时候,如果新读到的数小于等于堆顶,肯定不是我们要找的元素,只有新遍历到的数大于堆顶的时候,才将堆顶拿出,然后放入新读到的数,进而让堆自己去调整内部结构。

说明:这里最合适的操作其实是replace0,即直接把新读进来的元素放在堆顶,然后执行下沉
(siftDown0)操作。Java当中的PriorityQueue没有提供这个操作,只好先poll0再offer0。

优先队列的写法就很多了,这里只例举一个有代表性的,其它的写法大同小异,没有本质差别。

public int findKthLargest(int[] nums, int k) {
        if (nums == null || nums.length == 0)
            return -1;
        PriorityQueue<Integer> q = new PriorityQueue<>(k, (a, b) -> a - b);
        int len = nums.length;
        for (int i = 0; i < k; i++) {
            q.add(nums[i]);
        }

        for (int i = k; i < len; i++) {
            if (nums[i] > q.peek()){
                q.poll();
                q.add(nums[i]);
            }
        }
        return q.peek();
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值