358. K 距离间隔重排字符串
解题思路:
-
大根堆 + 队列 ,
-
1)首先 计数数组 统计 每个字符出现的次数 ,然后将 计数 > 0 的 字符 和 次数 一起放入 大根堆 ,大根堆中 按照字符的出现次数排序 。
-
2)遍历大根堆,每次取出大根堆的 堆顶 ,即出现 重复次数最多的字符 进行拼接。
-
3)拼接完后,对应 次数 - 1 ,同时加入到一个 队列 中, 如果 队列 大小为 k ,说明已经拼接了 k 个,从队列中出队 队首 元素,如果 队首元素的次数 > 0 就继续将其加入 大根堆 中。
-
4)最后当 大根堆为空 时,看 拼接的长度是否等于原长度 ,如果等于说明可以按 k 个间隔一组拼接完,否则说明还有元素挂在 队列 中,无法满足 k 个间隔一组。
-
注意:只有计数值大于 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) 单调递减队列中存储的是 下标 ,否则在判断队首是否超出了窗口左边界时,不好判断,因为 L 和 R 都是 下标 。
-
2)在收集答案时,可以创建一个长度 N - k + 1 的 int[] 数组(可以形成的窗口的个数)填充答案,也可以使用一个 ArrayList 收集,最后再转成 int[] 。
-
3) 单调递减队列可以使用 Deque<Integer> ,也可以使用一个 int[] 数组实现,但是要指定头尾指针 head==tail=0 , head 指向 队首 , tail 指向 队尾的下一个位置 ,用 head==tail 来表示 队列为空。
-
4) 单调递减队列也可以是一个 单调递减栈 ,思路是一样的,大于栈顶的不断弹栈,小于栈顶的压栈,在收集答案收缩窗口左边界时,则需要判断栈底是否超出左边界,如果超出就移除栈底。
这个代码还需要注意三点:
- 队列中存的是下标!队列中存的是下标!队列中存的是下标!所以在 while 循环中比较时以及在从队首取答案时,都要注意拿下标去nums[]中取值,不要直接使用队列中的元素值,这里很容易出错。
- 在从队首取答案时,只是查看队首,但不要弹出队首。(因为只有确定队首不在窗口内时才能弹出)
- 在收缩左边界 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 ,则需要同时 移除堆顶(这里要循环弹)