【LeetCode刷题笔记】堆和优先级队列

本文详细介绍了LeetCode中涉及堆和优先级队列的题目,包括358题的K距离间隔重排字符串、239题的滑动窗口最大值和295题的数据流中位数。通过大根堆和小根堆的使用,阐述了如何在数据结构中解决这类问题,并讨论了不同场景下优化算法的策略,如单调队列和计数排序。
摘要由CSDN通过智能技术生成

358. K 距离间隔重排字符串

解题思路:
  • 大根堆 + 队列
  • 1)首先 计数数组 统计 每个字符出现的次数 ,然后将  计数 > 0 的  字符 次数 一起放入 大根堆 ,大根堆中 按照字符的出现次数排序
  • 2)遍历大根堆,每次取出大根堆的 堆顶 ,即出现 重复次数最多的字符 进行拼接。
  • 3)拼接完后,对应 次数 - 1 ,同时加入到一个 队列 中, 如果 队列 大小为 k ,说明已经拼接了 个,从队列中出队 队首 元素,如果 队首元素的次数 > 0  就继续将其加入 大根堆 中。
  • 4)最后当 大根堆为空 时,看 拼接的长度是否等于原长度 ,如果等于说明可以按  个间隔一组拼接完,否则说明还有元素挂在 队列 中,无法满足  个间隔一组。
  • 注意:只有计数值大于 0 的才能入堆。

以下是整个算法的执行过程示意: 

 

 

 

 

 

 

 

我们发现整个算法过程就是在不断重复以下操作:每当大根堆中取出堆顶字符进行拼接以后,就将该字符放入队列中(同时计数-1),每当队列中满了 k 个以后,就弹出队列的队首再次加入大根堆(但是满足计数>0)。

我们思考一下,这里为什么要使用一个大根堆 + 一个队列呢?

  • 大根堆的主要作用:保证优先拼接词频数较大的字符,因为这个题目的主要问题点在于,要使相同字符间隔 k 个。假设我们先使用词频数较小的字符,那么剩下的就会是词频数较大的字符了,也就是说剩下的这些字符基本上都是重复的字符了,这样就不太容易满足间隔 k 个的要求了。(我觉得这里有一点点贪心思想)
  • 普通队列的作用:很显然,我们从大根堆中一边取一边拼接,得有个办法计数拼接了多少了,因为每当拼接了 k 个之后,我们就要重新开始拼接之前重复的字符。所以这个就是普通队列的作用之一,它的另外一个作用是暂存从大根堆中移出的字符,因为这些字符使用掉1次后,次数可能不会马上变成0,后面还得循环利用。(当然这里你用一个普通数组代替队列也能达到类似的目的)

239. 滑动窗口最大值

解题思路:
  • 1. 单调递减队列(或单调递减栈) 维护一个 单调递减队列 ,从 队首 队尾 是单调 递减 的,初始化窗口的左右边界 L = 0,R = 0 ,遍历数组,不断移动右边界 R++
  • 1)遇到 大于队尾 元素时,从 队尾一直弹出 ,遇到 小于队尾 元素时,从 队尾入队
  • 2) 当窗口大小  R - L + 1 == k  时,收集答案 :当前 队首下标 处的元素值即为 当前窗口内的最大值 ,同时收缩窗口的左边界 L++
  • 3) 收集答案之后还需要判断一下, 如果队首 下标 超出了窗口的 左边界 L ,则需要将 队首出队

 

注意:
  • 1) 单调递减队列中存储的是 下标 ,否则在判断队首是否超出了窗口左边界时,不好判断,因为 和  R 都是 下标
  • 2)在收集答案时,可以创建一个长度 N - k + 1 int[] 数组(可以形成的窗口的个数)填充答案,也可以使用一个 ArrayList 收集,最后再转成 int[]
  • 3) 单调递减队列可以使用 Deque<Integer> ,也可以使用一个 int[] 数组实现,但是要指定头尾指针 head==tail=0 head 指向 队首 tail 指向 队尾的下一个位置 ,用 head==tail 来表示 队列为空。
  • 4) 单调递减队列也可以是一个 单调递减栈 ,思路是一样的,大于栈顶的不断弹栈,小于栈顶的压栈,在收集答案收缩窗口左边界时,则需要判断栈底是否超出左边界,如果超出就移除栈底。

 这个代码还需要注意三点:

  1. 队列中存的是下标!队列中存的是下标!队列中存的是下标!所以在 while 循环中比较时以及在从队首取答案时,都要注意拿下标去nums[]中取值,不要直接使用队列中的元素值,这里很容易出错。
  2. 在从队首取答案时,只是查看队首,但不要弹出队首。(因为只有确定队首不在窗口内时才能弹出)
  3. 在收缩左边界 L++ 之后判断,如果队首 < L,也就是不在 [L, R] 范围内了,才弹出队首

使用数组的单调队列版本:

这个代码与前面的相比,res 的下标直接使用 L, 因为 L 代表窗口的左边界,所以窗口的大小是多少,L 就会移动多少次。

另外,关于窗口个数为什么是 N - k + 1

 

我们可以这么理解:首先在数组的最后预留出一个长度为 k = 3 的窗口,然后前面 7 个位置(即 N - k)以每一个位置做开头都可以形成 1 个 k=3 的窗口,所以窗口个数是 N - k + 1=7+1=8

解题思路:
  • 2. 大根堆 ,大根堆维护窗口内 k 个数字, 初始化窗口的左右边界 L = 0,R = 0 ,遍历数组,不断移动右边界 R++
  • 将遇到的每个元素的 下标 一同加入堆中,大顶堆 按值排序 值相等时按照下标排序
  • 当窗口大小  R - L + 1 == k  时,收集 堆顶 处的元素的值作为答案, 如果 堆顶 元素下标 小于窗口左边界 L  ,则需要同时 移除堆顶(这里要循环弹)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值