代码随想录算法训练营 day13 || 239. 滑动窗口最大值,347. 前K个高频元素

题解链接:

单调队列正式登场!| LeetCode:239. 滑动窗口最大值_哔哩哔哩_bilibili

优先级队列正式登场!大顶堆、小顶堆该怎么用?| LeetCode:347.前 K 个高频元素_哔哩哔哩_bilibili

解题状态:第一题在有了单调队列的思路后自行实现;第二题未能解出。

239. 滑动窗口最大值

思路:本题官方题解以及代码随想录所给出的代码中都用到了Deque这种双向队列,双向队列是一种首尾皆可出队与入队的特殊队列。这种工具非常值得学习,特别是我这个不会的人。回到这道题,由于我对双向队列的陌生,我认为与大多数栈与队列题目类似,栈与队列仅仅是一种数据存储的工具,因此可以采用任何容器结合静态指针实现栈与队列的模拟,甚至在一定程度上,会比声明Stack与Queue结构来的更加具有时间效率。

本题我选用了List实现了单调队列的实现。单调队列是一种非常优秀的工具。单调的关键就是元素必定严格单调的,任何入队元素从尾部入队,若违反了单调的特点,那么要么成为首元素,要么将不满足单调性的队列元素剔除,要么自身满足要求直接加入至队列的末尾。所以基于这三步我们就可以实现单调队列。

此外这道题的关键还在于给出滑动窗口,单调队列的首元素总是存储的最大或最小的值,本题理所当然是最大值,但是随着滑动窗口的移动,i位置的最大元素未能在后续的窗口位置成为最大值,因此我们需要考虑适当的将单调队列的首元素出栈,而这额外出队的操作,是源于滑动窗口一直在移动这个全局变数。

因此我们设置双指针,表示滑动窗口的前部与尾部,尾部为当前需要离开的元素,而首部为当前需要加入的元素。因此我们可以利用尾部指针去实现判断窗口单调队列内元素该出栈的时机。

此外我想强调,在窗口当前准备离开 j 位置时,其如果还在队列中,那么必是队列的首元素。其原因是这样,对于num[j] 存在这样两种情况,nums[j] 在单调队列中或者不在;那么我们仅需要去处理在队列中的情况。如果在队列中,而现在窗口正要离开这个元素,那么说明在窗口开始到达 j 位置时,并将他加入队列后,一直到窗口走过个位置,nums[j]都很好的存在与队列中,这说明,没有一个元素可以将他踢出去,结合前面的三点,只有一种情况可以解释,nums[j] 是 j-k+1 位置到 j 位置中最大的元素,那么我们很好办了,如果离开位置的元素发现还在队列中,那么仅仅可能是在队列首部,那么我们就出队首元素,这样队列中的元素个数必定不会超过滑动窗口的长度,如果这方面还有疑惑,我们下次开个文章继续讲。

下面给出我所实现的列表模拟单调队列。

// 时间复杂度O(n^2),空间复杂度O(2k)
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {

        if(k == 1)
            return nums;

        int[] res = new int[nums.length+1-k];
        List<Integer> list = new ArrayList<>();
        list.add(nums[0]);

        for(int i=1, j=1-k; i<nums.length; i++, j++){
            //  第一步 更新单调队列
            /* 判断队首元素是否该出队,这里的index判断,保证了单调队列中元素的个数不会超过k个,只有在i 的位置超过了当前滑动窗口范围的时候,
               才会要去队首元素进行考虑,以防队列内出现超过窗口大小数量的元素 
               j 位置是滑动窗口离开的元素,而i 此时是正在要进来的元素,至于为什么是remove 0 位置,
               !!!如果在窗口都要纳入新的元素了,旧党j 还在里面,说明j 后面的k-1 个位置的元素都小于j位置的元素,否则早就被清楚了
               单调队列真的是非常残酷,碰到大的,前面的人都要走的, */
            if(j>=0 && nums[j] == list.get(0))
                list.remove(0);
            // 判断完队首元素的合理性后,加入当前遍历元素
            // 第一种情况,取代队首,队列更新为1
            if(nums[i] > list.get(0))
                list.clear();
            
            // 第二种情况,是二把手到k把手中的一个,你把除队首外不如你的都干掉
            while(list.size()>0 && nums[i] > list.get(list.size()-1))
                list.remove(list.size()-1);
            
            // 第三种情况,你是队列里的老末,你就加进去吧,这也是前两种情况判断完之后都需要执行的一步
            list.add(nums[i]);

            //  第二步,记录最大值
            if(i >= k-1)
                res[i+1-k] = list.get(0);
        }
        return res;
    } 
}

347. 前K个高频元素

思路:从题目描述中我们可以明确这道题的最终目的就是统计数组中各个元素出现的次数,并将符合要求的元素输出。而经过前期的训练,显然用于判断元素是否重复的哈希表工具是这道题的好帮手,而且又是与次数相挂钩的题目,因此使用HashMap以键值对的形式保存元素以及次数显得非常的合适。但是这道题所提示的需要将时间复杂度控制O(n logn),显然这是对于处理结果时的排序做法进行了时间复杂度的限制。

堆排序、快排、合并排序都是满足要求的排序,因此在使用Map统计完元素之后,即可实现对于元素出现次数的排序,但是排序的过程我认为很繁琐,因为Map中value部分才是出现的次数,光是统计处所有的次数就是需要一次遍历,随后再进行排序的话,又是O(n logn)的开销。所以这道题就不是采用排序进行解题的一题。

本题学习到了队列和栈章节的新工具,堆,虽然这部分不属于栈与队列章节的知识,堆完全是在树章节后可展现的知识。但是我也第一次学习到了最大堆与最小堆的实现。

在此给出解题的代码一同学习。

// 时间复杂度O(k logk),空间复杂度O(k)
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        
        // 使用最小堆或者最顶堆来实现解题
        Map<Integer,Integer> map = new HashMap<>();
        int[] res = new int[k];
        // 统计nums数组内各个元素出现的次数
        for(int i=0; i<nums.length; i++)
           map.put(nums[i], map.getOrDefault(nums[i],0)+1);
        
        // 遍历map容器,将元素放入堆
        PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>(){
            @Override
            public int compare(Integer a, Integer b){
                return map.get(a) - map.get(b);
            }
        });
        // 保存的是前k个元素,而不是元素的次数
        for(Integer key:map.keySet()){
            if(pq.size() < k)
                pq.add(key);
            else if(map.get(key) > map.get(pq.peek())){
                pq.remove();
                pq.add(key);
            }
        }

        // 堆中所存储的就只有k个元素,与所需要返回的k个元素的数组刚好吻合
        for(int i=k-1; i>=0; i--){
            res[i] = pq.remove();
        }

        return res;
    }
}

此外在解题成功后的题解中,看到另一种使用数组进行出现次数统计的方法,非常非常非常值得学习!!!  在此与大家分享,共勉。

        int l = nums.length;
        int min = Integer.MAX_VALUE;
        int max = Integer.MIN_VALUE;
        for(int n : nums ){
            if( min > n ) min = n;
            if( n > max ) max = n;
        }
        int[] helps = new int[max-min+1];
        for(int n : nums ){
           helps[n-min]++;
        }

下一片文章会给出关于最大堆与最小堆在Java中的实现,给出具体的实现代码用作学习,这也是自我第一次学习Java中实现最大堆与最小堆的实现。以及另一道困难题的学习。

  • 24
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值