代码随想录算法训练营第11天 | 栈和队列部分: 239. 滑动窗口最大值,347. 前 K 个高频元素
栈和队列部分
239. 滑动窗口最大值
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值 。
思路:暴力方法,每次都计算窗口内的最值,时间复杂度O(n*k), 写不出来
答案:
创建一个单调队列,队头中保存队列中最大的元素,并且因为涉及到后面新加入的的元素的比较,所以我们采用双端队列的类来实现
首先,由于滑动窗口向前移动的时候,存在元素的移入和元素的移出,所以对应的我们的单调队列也应该发生相应的变化,需要对单调队列中的元素的移入和移出做出相应的策略
单调队列的入队方法:将待进入的元素和队尾的元素进行比较,如果大于队尾的元素的话,那么队尾的元素就出队,然后继续比较,如果不大于队尾的元素的话,就不需要改变(因为单调队列中存放的是滑动窗口中的最大值或者即将成为最大值的元素)
单调队列的出队方法:单调队列中的元素出队是因为滑动窗口中不再包含这个值了,而此时这个值还是窗口中的最大值,就需要对单调队列进行更新,此时队头的元素就是滑动窗口中最大的元素
class MyDeque {
//首先定义单调序列
//因为后面的处理会比较单调队列中队尾的元素,所以会使用使用双端队列
//Deque的具体实现:ArrayDeque, LinkedList
Deque<Integer> deque=new LinkedList<>();
//删除元素:为什么要删除元素呢
//理解理解,因为是滑动窗口,在往里面添加元素的同时,后面的部分也在划动着出去,所以需要判断滑动出去的那个元素是否是单调队列中队头觉得那个元素,如果是的话,那单调序列就只能出队了
public void delete(int val){
if(!deque.isEmpty() && val==deque.peek()){
deque.poll();//元素出队
}
}
//添加元素
//保护队列是单调的,从队列的入口开始比较,如果大于队列中的元素就让队列中的元素出队,直到满足单调性
public void add(int val){
//循环的删除
while(!deque.isEmpty() && val>deque.getLast()){
deque.removeLast();
}
deque.add(val);
}
//获取队列中的对头元素,也就是最大值
public int pek(){
return deque.peek();
}
}
class Solution{
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums.length==1){
return nums;
}
int len=nums.length-k+1;//滑动窗口的最后一个开始的位置
int[] res=new int[len];
int num=0;
MyDeque mdeque=new MyDeque();
for(int i=0;i<k;i++){
mdeque.add(nums[i]);
}
res[num++]=mdeque.pek();
for(int i=k;i<nums.length;i++){
mdeque.delete(nums[i-k]);//滑动窗口移除最后的一个元素
mdeque.add(nums[i]);//滑动窗口中添加一个元素之后单调队列的变化
res[num++]=mdeque.pek();
}
return res;
}
}
347. 前 K 个高频元素
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
思路:统计每个元素出现的次数,感觉使用hash表更方便,key存放数字,value存放出现的个数,但是这样的话,如何确定前k个高频的元素呢,key放元素出现的次数,value存放元素,但是这样也需要每次都遍历hash表来寻找最大的值????不会
答案:
题目涉及三块部分:
1.统计元素出现的频率 使用map 解决 这块思路正确
2.对频率进行排序 使用优先级队列(记忆)
3.找出前k个高频元素
PriorityQueue:采用什么方式实现频率的一个排序: 采用优先级队列的方法,优先级队列内部自动的按照权值的大小排序,默认是大顶堆的实现方式,即每次弹出一堆元素中的最大权值,因为本题中需要获取前k个高频的元素,如果使用大根堆的话,每次弹出最大值,所以本题采用小根堆的方式,当队列中的元素大于k时,就弹出元素
但是看代码的实现,感觉大根堆和小根堆都是类似的代码,看不出有什么区别
public int[] topKFrequent(int[] nums, int k) {
//将元素按元素值-元素的出现次数保存在map中
Map<Integer,Integer> map=new HashMap<>();
for(int i=0;i<nums.length;i++){
if(!map.containsKey(nums[i])){
map.put(nums[i],1);
}else{
map.put(nums[i],map.get(nums[i])+1);
}
}
//使用优先级队列对元素的出现次数进行排序
//采用大根堆的方式,即升序的模式
PriorityQueue<int[]> pq=new PriorityQueue<>((pair1,pair2)->pair2[1]-pair1[1]);
for(Map.Entry<Integer,Integer> entry:map.entrySet()){
pq.add(new int[]{entry.getKey(),entry.getValue()});
}
int[] ans=new int[k];
for(int i=0;i<k;i++){
ans[i]=pq.poll()[0];
}
return ans;
}
优先级队列这边的代码写不出来 待补充
补充:(pair1, pair2) ->pair[1]-pair2[1];代码是什么意思 是java中lamda形式的表达式
lamda就是匿名函数,匿名类不是->这种表现形式:使用场景:用于定义一个不需要被再次使用的类或者方法
使用说明:->左边是方法的参数,->右边是方法的返回值
补充:java类库:比较器