代码随想录训练营day13: 滑动窗口最大值, 前k个高频元素

滑动窗口最大值

这是做过的第一个困难题目, 虽然看起来不是很难, 但是有一些细节要注意

总体思路: 队列存的是下标, 并且数组中的数要从大到小排序

有三个重要点: 

  1. while循环判断, 如果nums[deque.peekLast]小于val, 那么就要removeLast, 同时把val加上

  2. 如果deque.peekFirst在nums[i-k]窗口之外了, 就要removeFirst

  3. 如果i ≥ k- 1也就是窗口被填满了, 那么就开始存max

细节:
1.首先需要一个队列, 一个数组, 一个默认值inde, 队列用来保存下标, 数组保存最大值, index用来给数组计数

2.关键就是让这个窗口移动, 向右移要判断之后pollLast, 然后offerLast

3. 首先用一个while, 然后窗口的pollFirst和添加数组用if就行了

4.记住这里最后的数组长度就是nums.length - k + 1, 也就是滑动窗口的个数

5. i - k + 1代表着这个窗口左侧, 需要判断队列的first在不在窗口内 这里要包括等于号啊

6.最后的判断别忘了边界处理大于等于窗口大小

总结,滑动窗口三步走

  1. 判断如何在右边添加数字, 判断如果小了就poll掉
  2. 判断如何在左边边界移动, 判断如果小于窗口就去掉
  3. 判断是否填满窗口, 满足就return peek
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        //存下标可以更方便的来确定元素是否需要移出滑动窗口
        //判断下标是否合法来确定是否要移出
        Deque<Integer> q = new LinkedList<>();
        //res就是最后的长度, 也就是总共窗口的长度
        int[] res = new int[nums.length - k + 1] ;
        int index = 0;
        for(int i = 0; i < nums.length; i++){
            //保证队列的单调递减,使队列的出口始终为最大值(窗口右移)
            //注意队列存的是数组下标,所以判断逻辑是nums[i] > nums[q.peekLast()]
            //容易误写成nums[i] > q.peekLast()
            while(!q.isEmpty() && nums[i] > nums[q.peekLast()]){
                q.pollLast();
            }
            q.offerLast(i);
            //判断队列出口的值是否合法,如果值的下标不在窗口内则要将其移出
            if(q.peek() <= i - k){
                q.pollFirst();
            }
            //窗口至少填满一次后才开始放最大值
            //依然要注意队列存的是下标,所以赋值是赋nums[q.peekFirst()]
            if(i >= k - 1){
                res[index++] = nums[q.peek()];
            }            
        }
        return res;
    } 
}

前k个高频元素

 这道题思路其实不难, 但是设计很多基础的数据结构知识, 我都不熟悉, 导致做得很折磨

思路(大顶堆): 复杂度nLogn

  1. 先遍历数组, 用map收集key和value
  2. 然后把map的元素放进优先队列
  3. 最后创建一个新的数组, 遍历前k个数据就行了

细节:

1. 创建优先队列涉及到lambda表达式和comparator

2.最后遍历数据没有理解结构pq.poll()[0]是什么意思

这里先上代码, 需要把排序和集合巩固之和才能完全理解, lambda和comparator如下 

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
				//先把数组的key和value放到哈希表里面
        HashMap<Integer, Integer> hash = new HashMap<>();
        for(int num : nums){
            if(hash.containsKey(num)){
                hash.put(num, hash.get(num) + 1);
            } else{
                hash.put(num, 1);
            }
        }
				//然后把哈希表的数字放到优先队列里, 这里涉及到lambda表达式
        PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2) -> pair2[1] - pair1[1]);
        for(Map.Entry<Integer, Integer> entry : hash.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;
    }
}

小顶堆: 复杂度nLogk

由于时间关系暂时不搞了, 周末再来看

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<>((pair1,pair2)->pair1[1]-pair2[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;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值