LeetCode 239 滑动窗口最大值
题目简析:整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位,返回 滑动窗口中的最大值。
思路简析:摘录了代码随想录里核心的部分
队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队里里的元素数值是由大到小的。
设计单调队列的时候,pop,和push操作要保持如下规则:
- pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作
- push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止
保持如上规则,每次窗口移动的时候,只要问que.front()就可以返回当前窗口的最大值。
其余的内容在代码注释里我认为是足够有助于我日后回顾的了,也是理解了一遍卡哥的思路后自己动手写了出来,有想看的朋友也不要嫌弃我注释很长hhhhh。
class MyDeque {
Deque<Integer> deque = new LinkedList<>();
//编写弹出
void poll(int val){
//前一个:空则无数值可以弹出
//后一个:因为是先把前k个填入了窗口进行比较,所以直到第k个,都不进行弹出
if(!deque.isEmpty() && val == deque.peek()){
deque.poll();
}
}
//编写入队
void add(int val){
//发现新填入的数值大于末尾元素,就把末尾元素给弹出再加入,否则正常加入
while(!deque.isEmpty() && val > deque.getLast()){
deque.removeLast();
}
deque.add(val);
}
//返回最大值----代表最大值的队列前端
int peek(){
return deque.peek();
}
}
public int[] maxSlidingWindow(int[] nums, int k) {
//限制一下
if(nums.length == 1){
return nums;
}
MyDeque myDeque = new MyDeque();
//无需创建length个,最后窗口会排出k-1个值
int len = nums.length;
int []arr = new int[len - k + 1];
//滑动窗口最大值下标
int count = 0;
//为保证窗口有值,先将前k个放入滑动窗口
for (int i = 0; i < k; i++) {
myDeque.add(nums[i]);
}
//此时已经产生了一个窗口最大值,直接放入
arr[count++] = myDeque.peek();
for (int i = k; i < len; i++) {
//先将窗口进行移动
myDeque.poll(nums[i-k]);
//因为传的是i-k,而不断传值后若没遍历到最大值的位置
//说明上一个最大值还存在在新的窗口内
//eg
//窗口: [ 1 -1 3 ] -2 5
//先将前k个入队
//队列情况应该是:1 -1 ----add中的---removeLast--> 1 3
//第二轮窗口: 1 [ -1 3 -2] 5
//num[i-k] = 1
//但是已经不在队列里,经过poll的判断 (val==1) == (deque.peek() == 1)
//因此排出窗口的1 队列经过add后剩: 3 -2
//下一轮也是一样的
//第三轮窗口: 1 -1 [ 3 -2 5]
//队列情况:① 3 -2 --add中的removeLast---> ② 3(5>3继续removeLast) ③经过add后剩 5
//num[i-k] = -1 也仍然不在,排出窗口的-1
//直到num[i-k] == 3 此时说明窗口已经划过3,因此可以把3给排出去了
//不断滑动窗口,填入新数
myDeque.add(nums[i]);
//加入后已经产生最大值
arr[count++] = myDeque.peek();
}
return arr;
}
LeetCode 347前K个高频元素
public int[] topKFrequent2(int[] nums, int k) {
Map<Integer,Integer> map = new HashMap<>();//key为数组元素值,val为对应出现次数
for(int num:nums){
map.put(num,map.getOrDefault(num,0)+1);
}
//在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
//出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆)
PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(pair -> pair[1]));
for(Map.Entry<Integer,Integer> entry:map.entrySet()){
//小顶堆只需要维持k个元素有序
if(pq.size()<k){//小顶堆元素个数小于k个时直接加
pq.add(new int[]{entry.getKey(),entry.getValue()});
}else{
if(entry.getValue()>pq.peek()[1]){
//当前元素出现次数大于小顶堆的根结点(这k个元素中出现次数最少的那个)
pq.poll();
//弹出队头(小顶堆的根结点),即把堆里出现次数最少的那个删除
//留下的就是出现次数多的了
pq.add(new int[]{entry.getKey(),entry.getValue()});
}
}
}
int[] ans = new int[k];
for(int i=k-1;i>=0;i--){
//依次弹出小顶堆,先弹出的是堆的根,出现次数少,后面弹出的出现次数多
ans[i] = pq.poll()[0];
}
return ans;
}