算法训练营day13(栈和队列03:滑动窗口中的最大元素,数组中前k个高频元素,总结)

往日任务
● day 1 任务以及具体安排:https://docs.qq.com/doc/DUG9UR2ZUc3BjRUdY  
● day 2 任务以及具体安排:https://docs.qq.com/doc/DUGRwWXNOVEpyaVpG  
● day 3 任务以及具体安排:https://docs.qq.com/doc/DUGdqYWNYeGhlaVR6 
● day 4 任务以及具体安排:https://docs.qq.com/doc/DUFNjYUxYRHRVWklp 
● day 5 休息
● day 6 任务以及具体安排:https://docs.qq.com/doc/DUEtFSGdreWRuR2p4 
● day 7 任务以及具体安排:https://docs.qq.com/doc/DUElCb1NyTVpXa0Jj 
● day 8 任务以及具体安排:https://docs.qq.com/doc/DUGdsY2JFaFhDRVZH 
● day 9 任务以及具体安排:https://docs.qq.com/doc/DUHVXSnZNaXpVUHN4 
● day 10 任务以及具体安排:https://docs.qq.com/doc/DUElqeHh3cndDbW1Q 
●day 11 任务以及具体安排:https://docs.qq.com/doc/DUHh6UE5hUUZOZUd0 
●day 12 休息 

第五章 栈与队列part03
 今日内容: 
 
● 239. 滑动窗口最大值
● 347.前 K 个高频元素
● 总结
 
 详细布置 
 
 
 239. 滑动窗口最大值 (一刷至少需要理解思路)
 
之前讲的都是栈的应用,这次该是队列的应用了。
 
本题算比较有难度的,需要自己去构造单调队列,建议先看视频来理解。 
 
题目链接/文章讲解/视频讲解:https://programmercarl.com/0239.%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.html 
 
 
 347.前 K 个高频元素  (一刷至少需要理解思路)
 
大/小顶堆的应用, 在C++中就是优先级队列 
 
本题是 大数据中取前k值 的经典思路,了解想法之后,不算难。
 
题目链接/文章讲解/视频讲解:https://programmercarl.com/0347.%E5%89%8DK%E4%B8%AA%E9%AB%98%E9%A2%91%E5%85%83%E7%B4%A0.html  
 
 
 总结 
 
栈与队列做一个总结吧,加油
 
https://programmercarl.com/%E6%A0%88%E4%B8%8E%E9%98%9F%E5%88%97%E6%80%BB%E7%BB%93.html  
 

day13

滑动窗口最大值

单调队列

用deque实现,deque有加入推出看队列顶add(),poll(),peek(),队列顶也就是出口

还有getLast()和removeLast()移除队尾,队尾也就是入口

单调队列是一种思想,具体实现的模型要自己具体问题具体分析

//答案
 //自定义数组
 class MyQueue {
     Deque<Integer> deque = new LinkedList<>();
     //弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
     //同时判断队列当前是否为空
     void poll(int val) {
         if (!deque.isEmpty() && val == deque.peek()) {
             deque.poll();
         }
     }
     //添加元素时,如果要添加的元素大于入口处的元素,就将入口元素弹出
     //保证队列元素单调递减
     //比如此时队列元素3,1,2将要入队,比1大,所以1弹出,此时队列:3,2
     void add(int val) {
         while (!deque.isEmpty() && val > deque.getLast()) {
             deque.removeLast();
         }
         deque.add(val);
     }
     //队列队顶元素始终为最大值
     int peek() {
         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;
         //自定义队列
         MyQueue myQueue = new MyQueue();
         //先将前k的元素放入队列
         for (int i = 0; i < k; i++) {
             myQueue.add(nums[i]);
         }
         res[num++] = myQueue.peek();
         for (int i = k; i < nums.length; i++) {
             //滑动窗口移除最前面的元素,移除是判断该元素是否放入队列
             myQueue.poll(nums[i - k]);
             //滑动窗口加入最后面的元素
             myQueue.add(nums[i]);
             //记录对应的最大值
             res[num++] = myQueue.peek();
         }
         return res;
     }
 }
 ​
 //自己写
 class Solution {
     public int[] maxSlidingWindow(int[] nums, int k) {
         int len = nums.length - k + 1;
         int[] res = new int[len];
         MyQueue q = new MyQueue();
         for(int i = 0; i<k; i++){
             q.add(nums[i]);
         }
         int index = 0;
         res[index++] = q.peek();//运行完就要index++
         for(int i = k; i < nums.length; i++){
             q.add(nums[i]);
             q.poll(nums[i - k]);//队列里应该被扔出去的那个元素,如果就在q的出口处,就把他扔出去
             res[index++] = q.peek();
         }
         return res;
     }
 }
 class MyQueue{
     Deque<Integer> deque = new LinkedList();//LinkedList去实现deque
     public void poll(int val){
         //出口位置元素和val一样就推出
         if(!deque.isEmpty() && deque.peek() == val){
             deque.poll();
         }
     }
     public void add(int val){
         while(!deque.isEmpty() && val > deque.getLast() ){
             deque.removeLast();
         }
         deque.add(val);
     }
     public int peek(){
         return deque.peek();
     }
 }

前k个高频元素

优先级队列

pq实现小顶堆,维护k个高频元素;小顶堆每次弹出堆顶,也就是最小的元素

时间复杂度nlogk,n是因为数组遍历,logk是因为小顶堆二叉树维护k个元素logk

pq方法有加入推出offer(),poll()

//答案:
 class Solution {
     public int[] topKFrequent(int[] nums, int k) {
         // 优先级队列,为了避免复杂 api 操作,pq 存储数组
         // lambda 表达式设置优先级队列从大到小存储 o1 - o2 为从小到大,o2 - o1 反之
         PriorityQueue<int[]> pq = new PriorityQueue<>((o1, o2) -> o1[1] - o2[1]);
         //pq优先级队列,里面需要储存数字和频率,所以考虑数组或者键值对var都可以
         //pq构建的时候传入比较器,o1是数组,o1[1]里面放频率,根据频率排序,返回值为负数则第一个元素放在前面
         //o1[1] - o2[1],返回负数说明o1小,o1放前面,也就是从小到大排序
         int[] res = new int[k]; // 答案数组为 k 个元素
         Map<Integer, Integer> map = new HashMap<>(); // 记录元素出现次数
         for (int num : nums) map.put(num, map.getOrDefault(num, 0) + 1);
         for (var x : map.entrySet()) { // entrySet 获取 k-v Set 集合
             // 将 kv 转化成数组
             int[] tmp = new int[2];
             tmp[0] = x.getKey();
             tmp[1] = x.getValue();
             pq.offer(tmp);//offer就是加进去
             // 下面的代码是根据小根堆实现的,我只保留优先队列的最后的k个,只要超出了k我就将最小的弹出,剩余的k个就是答案
             if(pq.size() > k) {
                 pq.poll();
             }
         }
         for (int i = 0; i < k; i++) {
             res[i] = pq.poll()[0]; // 获取优先队列里的元素
         }
         return res;
     }
 }
 //自己写
 class Solution {
     public int[] topKFrequent(int[] nums, int k) {
         //1.先统计频率
         //2.用从小到大的优先级队列维护k个高频
         Map<Integer,Integer> map = new HashMap<>();//HashMap实现map
         for(int num : nums){
             map.put(num,map.getOrDefault(num, 0) + 1);
         }
         PriorityQueue<int[]> pq = new PriorityQueue<>((o1,o2) -> o1[1] - o2[1]);
         //队列头到队列尾从小到大
         for( var entry : map.entrySet()){
             //遍历键值对
             int[] temp = new int[2];
             temp[0] = entry.getKey();
             temp[1] = entry.getValue();
             pq.offer(temp);
             if( pq.size() > k){
                 pq.poll();
             }
         }
         int[] res = new int[k];
         //从后往前遍历res,不断出队即可,没有遍历队列这回事儿
         for( int i = k - 1; i >= 0;i--){
             res[i] = pq.poll()[0];
         }
         return res;
     }
 }

总结:代码随想录

感谢大佬分享:

代码随想录-算法训练营day13【栈与队列03:滑动窗口最大值、前K个高频元素、简化路径、总结】-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值