算法通过村第十四关-堆|白银笔记|经典问题

news2024/11/19 16:32:36

文章目录

  • 前言
  • 在数组中寻找第K大的元素
  • 堆排序原理
  • 合并K个排序链表
  • 总结


前言


提示:想要从讨厌的地方飞出来,就得有藏起来的翅膀。 --三岛由纪夫《萨德侯爵夫人》

这里我们主要看一下经典的题目,这三个题目来说都是堆的热点问题。重点再理解处理方式就行。

在数组中寻找第K大的元素

参考题目地址:215. 数组中的第K个最大元素 - 力扣(LeetCode)
在这里插入图片描述

在这里插入图片描述

这个题目的道理非常简单,主要的方法有三种:

  1. 选择法
  2. 堆查找法
  3. 快速排序法

选择法很简单,就是遍历一边找到最大的元素,然后再遍历一遍找第二大的,然后再遍历一遍找第三大…直到第K次,就可以找到目标值。但是这种方法只适合面试的时候预热,面试官不会让你写这么简单的代码,因为这个方法的时间复杂度为O(NK)。

比较好的方法就是堆排序和快速排序。快速排序我们已经分析过了,这里看看堆排序的,看看怎么解决。

快排推荐⭐⭐⭐⭐:算法通过村第十关-快排|青铜笔记|快排也没那么难-CSDN博客

其实这个题目采用大顶堆和小顶堆都是可以解决的,但是我们这里推荐**“找最大用最小,找最小用最大”**,找中间用两个堆呗,这样更容易理解,适用的范围也更广。我们构造一个大小只有4的小顶堆,为了更好说明问题,我们扩展以下序列:【3,2,3,1,2,4,5,1,5,6,2,3】。

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

另外元素进入的时候,先替换根元素,如果发现左右两个子树都小该怎么办呢?很显然应该是更小的那个比较,这样才能保证根元素一定是当前堆最小的。假如两个子孩的值一样呢?那就随便选一个。

在这里插入图片描述

新元素插入的时候只是替换根元素,然后重新构造小顶堆,完成之后,你会神奇的发现此时根的元素正好是第四大的元素。

这时候你会发现,不管要处理多大的序列,或者是不是固定的,根元素每次都是恰好是当前序列下的第K大的元素。上面图的篇幅优先,注意省略了一部分调成的环节,这里好好看看。

上面的代码自己实现起来非常困难,我们可以借助JDK的优先队列来解决,其思路是很简单的。由于第K大的元素,其实就是整个数组排序以后后面半部分最小的那个元素,这里就可以注意,我们可以维护一个有K个元素的最小堆:

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

说明:这里最适合的操作其实是replace(),即直接把新读到的元素放入堆顶,然后执行下沉(siftDown())操作。Java中PriorityQueue没有提供这个操作,只好先poll再offer

优先队列的写法有很多,这里只例举一个有代表性,其他的写法都差不多,没有本质区别。

看代码如下:

    /**
     * 数组中的第K个最大元素
     * @param nums
     * @param k
     * @return
     */
    public static int findKthLargest(int[] nums, int k) {
        // 当然k不合理,就直接结束
        if (k > nums.length) {
            return -1;
        }
        // 获取数组长度
        int n = nums.length;
        // 创建包含k个元素的小顶堆
        PriorityQueue<Integer> minHeap = new PriorityQueue<>(k, (a, b) -> a - b);
        for (int i = 0; i < k; i++) {
            minHeap.add(nums[i]);
        }
        for (int i = k; i < n; i++) {
            // 获取堆顶元素 比较是否需要替换
            Integer topEle = minHeap.peek();
            // 这里只有大于 才能进
            if (nums[i] > topEle) {
                minHeap.poll();
                minHeap.offer(nums[i]);
            }
        }
        return minHeap.peek();
    }

堆查找与一般的查找一个显著的优势点是可以对于超大数量的数据进行查找,还能堆数量位置的流数据进行查找。推荐一个题目⭐⭐⭐⭐:

703. 数据流中的第 K 大元素 - 力扣(LeetCode)

这里重要的是记住:找第k大用小顶堆,找第K小用大顶堆。

具体来说:

k多大就建立多大的固定堆
找最大用小顶堆
只和根元素比较,满足条件在能进去

堆排序原理

查找:找小用大,找大用小

排序:升序用小,降序用大

前面介绍了如何使用堆来进行特殊情况的查找,堆的另一个很重要的作用就是排序,那么要怎么排序呢?其实非常简单,我们直到再大顶堆中,根节点是整个结构最大的元素,我们将其拿走,剩下的元素将会重排,此时根节点的第二大的元素,我们再拿走,依次类推。最后堆只剩一个元素的时候,是不是拿走的数据也就排好了?

具体来说,建堆结束之后,数组中的数据已经按照大顶堆的特性来组织了,数组中的第一个元素就是堆顶,也就是最大元素,我们只要他和最后一个元素交换,那个最大元素就放到下标为n的位置上了。

这个过程上面有点类型“删除堆顶元素”的操作,当堆顶元素移除之后,我们把剩下标为n的元素放到堆顶,然后再通过堆的结构化调整,将剩下的n - 1个元素重新构建成堆,堆调整之后,我们再去取元素,这样一直循环,直至重复下去,直到堆最后剩下一个元素,也就是排序完成了。

当然再上面的过程用,放到最后一个位置的元素就不参与排序和计算了。

看一个例子,我们对一个序列进行排序[2,21,4,53,64,78,90,102],先构造大顶堆,然后然根元素出堆,继续调整大顶堆:
在这里插入图片描述
这时候你会发现出堆的序列刚好是:102,90,78,64,53…。也就是从大到小排列。

所以这里可以明白了,如果是小顶堆的化,自然是升序的。所以再排序的时候:

升序用小,降序用大。

记住这个对解题很有用。

合并K个排序链表

参考题目介绍:23. 合并 K 个升序链表 - 力扣(LeetCode)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这个问题的解法五花八门,我们看下用堆排序要怎么处理,因为每个队列都是从小到大排序的,我们每次需要拿到最小值,也就是说我们需要使用小顶堆,构建党法和操作与大顶堆完全一样,不同的是每次比较谁更小。使用堆和并的策略是不过几个链表,最终都是按照顺序来的。每次都是剩余节点的最小值加到输出链表的尾部,然后进行堆的调整,最后合并就完成了。

还有一个问题,这个堆应该有多大呢,给了对少个链表,堆就定义多大。

    /**
     * 合并 K 个升序链表
     *
     * @param lists
     * @return
     */
    public ListNode mergeKLists(ListNode[] lists) {
        if (lists.length == 0 || lists == null) {
            return null;
        }
        // 创建堆
        PriorityQueue<ListNode> q = new PriorityQueue<ListNode>(Comparator.comparing(node -> node.val));
        for (int i = 0; i < lists.length; i++) {
            if (lists[i] != null) {
                q.add(lists[i]);
            }
        }
        // 虚拟节点
        ListNode dummy = new ListNode(0);
        ListNode tail = dummy;

        while (!q.isEmpty()) {
            tail.next = q.poll();  // 取最小
            tail = tail.next;      // 链接下一个
            if (tail.next != null) { // 判断是否到底
                q.add(tail.next);  // 重复下一个
            }
        }
        return dummy.next;
    }

总结

提示:堆经典问题;大顶堆和小顶堆;手绘原理;堆排序解析;堆查询特点


如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ ("▔□▔)/

如有不理解的地方,欢迎你在评论区给我留言,我都会逐一回复 ~

也欢迎你 关注我 ,喜欢交朋友,喜欢一起探讨问题。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1095049.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt不能安装自己想要的版本,如Qt 5.15.2

使用在线安装工具安装Qt5.15.2时&#xff0c;发现没有Qt 5的相关版本&#xff0c;只有Qt 6的版本&#xff0c;这时选择右边的Archive&#xff0c;再点击筛选&#xff0c;这时就会出现之前的Qt版本。

vscode插件路径转移C盘之外盘

改变vscode系统路径 最近C盘路径不够了&#xff0c;网上的工具使用没那么精细&#xff0c;还不如自己手动看每个文件夹大小。在整理过长遇到vscode插件路径转移&#xff0c;方法如下&#xff1a; 桌面图标右键点击属性 改变–extensions-dir后面参数就可以了。

Web3 整理React项目 导入Web3 并获取区块链信息

上文 WEB3 创建React前端Dapp环境并整合solidity项目&#xff0c;融合项目结构便捷前端拿取合约 Abi 我们用react 创建了一个 dapp 项目 并将前后端代码做了个整合 那么 我们就来好好整理一下 我们的前端react的项目结构 我们在 src 目录下创建一个 components 用来存放我们的…

Python学习----Day07

函数 函数是组织好的&#xff0c;可重复使用的&#xff0c;用来实现单一&#xff0c;或相关联功能的代码段。函数能提高应用的模块性&#xff0c;和代码的重复利用率。你已经知道Python提供了许多内建函数&#xff0c;比如print()。但你也可以自己创建函数&#xff0c;这被叫做…

C++ 程序员入门需要多久,怎样才能学好?

文章目录 C学习方案有哪些推荐的在线教程或学习资源可以帮助我学习C编程&#xff1f;你能给我一些关于C内存管理的进阶学习资源吗&#xff1f; AI解答 C学习方案 C是一种功能强大且广泛应用的编程语言&#xff0c;作为一个初学者&#xff0c;学习C需要一定的时间和努力。学习…

【java学习—七】对象的实例化过程(33)

文章目录 1. 简单类对象的实例化过程2. 子类对象的实例化过程 1. 简单类对象的实例化过程 2. 子类对象的实例化过程

YOLO目标检测——打电话数据集【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;安全监控、智能驾驶、人机交互、智能城市数据集说明&#xff1a;YOLO目标检测数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富。使用lableimg标注软件标注&#xff0c;标注框质量高&#xff0c;含voc(xml)、coco(json)和yolo(txt…

快速学习MyBatisPlus

文章目录 前言一、条件构造器和常用接口1.wapper介绍2.QueryWrapper&#xff08;1&#xff09;组装查询条件&#xff08;2&#xff09;组装排序查询&#xff08;3&#xff09;组装删除查询&#xff08;4&#xff09;条件优先级&#xff08;5&#xff09;组装select子句&#xf…

c++命名空间,缺省参数,引用

首先为了解决命名冲突&#xff0c;c提出了命名空间这一功能 比如using namespace std; 就是使用std&#xff08;c官方库定义的命名空间&#xff09;这个命名空间里面的命名。 using就可以直接指定本文件用那个命名空间。 也可以用::域作用限定符 如std::cin>> 并且会…

Linux网络编程系列之服务器编程——信号驱动模型

一、什么是信号驱动模型 在服务器中&#xff0c;信号驱动模型是一种事件处理模型&#xff0c;它能够异步地响应来自外部的事件。服务器可以注册一组回调函数&#xff0c;来处理来自客户端或其他进程的信号或事件&#xff0c;当信号或事件触发时&#xff0c;操作系统会通知服务器…

云耀服务器L实例部署Nextcloud企业云盘系统|华为云云耀云服务器L实例评测使用体验

文章目录 Nextcloud简介1.1 部署华为云云耀服务器L实例1.1.1 云耀服务器L实例购买1.1.2 云耀服务器L实例初始化配置1.1.3 远程登录云耀服务器L实例 2. 云耀服务器L实例中间件部署2.1 安装配置环境2.1.1 安装基本工具2.1.2 安装MariaDB2.1.3 安装Nginx2.1.4 安装PHP 3. 安装Next…

网络通信协议-HTTP、WebSocket、MQTT的比较与应用

在今天的数字化世界中&#xff0c;各种通信协议起着关键的作用&#xff0c;以确保信息的传递和交换。HTTP、WebSocket 和 MQTT 是三种常用的网络通信协议&#xff0c;它们各自适用于不同的应用场景。本文将比较这三种协议&#xff0c;并探讨它们的主要应用领域。 HTTP&#xff…

【VSCode】Windows环境下,VSCode 搭建 cmake 编译环境(通过配置文件配置)

除了之前的使用 VSCode 插件来编译工程外&#xff0c;我们也可以使用配置文件来编译cmake工程&#xff0c;主要依赖 launch.json 和 tasks.json 文件。 目录 一、下载编译器 1、下载 Windows GCC 2、选择编译器路径 二、配置 debug 环境 1、配置 lauch.json 文件 2、配置…

FPGA project : flash_write

本实验重点学习了&#xff1a; flash的页编程指令pp。 在写之前要先进行擦除&#xff08;全擦除和页擦除&#xff09;&#xff1b; 本实验&#xff1a;先传写指令&#xff0c;然后进入写锁存周期&#xff0c;然后传页编程指令&#xff0c;3个地址&#xff1b; 然后传数据&a…

分布式事务协调中间件---seata快速入门

分布式事务 Seata&#xff0c;之前叫做Fescar&#xff0c;是一个开源的分布式事务解决方案&#xff0c;它主要致力于提供高效和简单的分布式事务服务。Seata主要用于解决微服务架构下的数据一致性问题。 Seata 的基本原理是基于两阶段提交 (2PC) 以及三阶段提交 (3PC)&#xff…

Linux 学习的六个过程

Linux 上手难&#xff0c;学习曲线陡峭&#xff0c;所以它的学习过程更像一个爬坡模式。这些坡看起来都很陡&#xff0c;但是一旦爬上一阶&#xff0c;就会一马平川。 1、抛弃旧的思维习惯&#xff0c;熟练使用 Linux 命令行 在 Linux 中&#xff0c;无论我们做什么事情&…

快速排序、归并排序、基数排序

快速排序 算法思想 图 1-1 即确定一个基准值&#xff08;一般为数组中间位置的元素&#xff0c;或者自定义&#xff09;&#xff0c;让待排序数组中所有比基准值小的元素放到基准值左边的位置&#xff0c;所有比基准值大的元素放到基准值右边的位置&#xff0c;这样一趟排序下…

Rxjava3 全新详解及常用操作符

简介 RxJava 是一个基于 Java 的响应式编程库&#xff0c;用于处理异步事件流和数据流。它是由 Netflix 开发并开源&#xff0c;现在广泛用于 Android 和 Java 后端开发。RxJava 提供了一种用于组合和处理异步数据的丰富工具集&#xff0c;它的核心思想是将数据流视为一系列事…

Hadoop3教程(十):MapReduce中的InputFormat

文章目录 &#xff08;87&#xff09;切片机制与MapTask并行度决定机制&#xff08;90&#xff09; 切片源码总结&#xff08;91&#xff09;FileInputFormat切片机制&#xff08;92&#xff09;TextInputFormat及其他实现类一览&#xff08;93&#xff09; CombineTextInputFo…

深入解析C语言中的strstr函数

目录 一&#xff0c;strstr函数简介 二&#xff0c;strstr函数实现原理 三&#xff0c;strstr函数的用法 四&#xff0c;strstr函数的注意事项 五&#xff0c;strstr函数的模拟实现 一&#xff0c;strstr函数简介 strstr函数是在一个字符串中查找另一个字符串的第一次出现&…