代码随想录Day13-栈与队列03 239. 滑动窗口最大值

文章介绍了如何使用单调队列解决滑动窗口最大值问题,解释了为什么不能使用大顶堆以及双端队列在此问题中的应用。另外,对于找出数组中前k个高频元素,文章提出了使用HashMap统计频率,然后排序的方法,以及使用小顶堆优化的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

239. 滑动窗口最大值

        好难啊,没想出来,看了题解动画演示的第一个,感觉讲的挺清楚的,附个看了题解之后写的代码和思路:

        思路还是直接看动画演示把~动画演示 单调队列 239.滑动窗口最大值 - 滑动窗口最大值 - 力扣(LeetCode)

        大概意思就是维护一个双端队列(单调队列)始终存储可能是滑动窗口最大值的元素下标,每次输出队列的队首元素。感觉这句话信息量有点大。。。

        先说说为什么不能用大顶堆。一开始写的就是大顶堆(因为是从第二个题开始写的,就有点想套一下)。。然后错了。。看了输出才发现咋回事。。大顶堆会改变元素本身的位置,也就是说本来是

[1,3,-1,-3,5,3,6,7]

用了堆之后这组数的前三个顺序就变成了

[3,1,-1]

滑动窗口往下走第二个最大值就直接不对了。

        第二点使用双端队列的原因是,存在元素需要从队尾出队的情况。具体看注释叭

        双端队列可以用的api看这篇~Java中Deque双端队列的api总结_sqzr316的博客-优快云博客

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        //存最终结果
        int[] result = new int[nums.length - k +1];
        //创建双端队列
        LinkedList<Integer> list = new LinkedList<>();

        //遍历数组元素,右指针指向滑动窗口右边界
        for(int right = 0; right < nums.length; right++){
            //队列非空且当前元素大于队尾元素,去掉队尾元素直到当前元素小于队尾元素
            while(!list.isEmpty() && nums[list.peekLast()] < nums[right]){
                list.pollLast();
            }

            //每次遍历都会将当前元素下标加入队列中
            list.offerLast(right);

            //计算滑动窗口左边界
            int left = right - k + 1;

            //如果队头元素超出左边界,队头元素出队
            if(list.peekFirst() < left){
                list.pollFirst();
            }

            //从产生第一个长度为k的滑动窗口开始,把队列中最大元素添加到result中
            if(right + 1 >= k){
                result[left] = nums[list.peekFirst()];
            }
        }

        return result;
        
    }
}

347. 前 K 个高频元素

        想法是统计每个数字出现的次数,把次数按照从大到小排好序,再取排序好的前k个元素,就是整数数组里出现频率前k高的元素了。

        具体实现:用HashMap装数字(key)和该数字出现次数(value),对次数(value)进行排序,排序好的entrySet(包含映射关系的视图)键值对放在list集合中,最后把list集合的前k个entrySet中的key(最后要返回的数字)装到一个数组里返回就行了。

    hashmap.getOrDefault(Object key, V defaultValue):返回 key 相映射的的 value,如果给定的 key 在映射关系中找不到,则返回指定的默认值。

        Map中采用Entry内部类来表示一个映射项,映射项包含Key和Value,就是一个键值对。Java中Map的 entrySet() 详解以及用法(四种遍历map的方式)_NO0b的博客-优快云博客

        map.entrySet() 方法返回映射中包含的映射的 Set 视图。注意:Set 视图意思是 HashMap 中所有的键值对都被看作是一个 set 集合。

        Collections类中的sort方法可以实现对List接口的集合进行排序,但不能对键值对中的值进行排序。因此想要比较value的大小,要重写sort方法中的Comparator接口的compare方法,格式:

        Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
           @Override
           public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
               return o2.getValue().compareTo(o1.getValue());
           }
       });

        关于sort方法可以参考这篇~【Java】Collections.sort() 方法 —— Comparable、Comparator接口_sort方法是哪个接口_杨丹的博客的博客-优快云博客

        HashMap排序可以参考这篇~Java HashMap按key排序和按value排序的两种简便方法_hashmap没有顺序,要让映射关系根据键排序,使用哪种映射类呢? 来自课件““讨论3”_xHibiki的博客-优快云博客

        下面是用lambda表达式化简过的

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        //先把数全放到map集合中,统计一个数出现了几次
        Map<Integer,Integer> map = new HashMap<>();
        for(int i: nums){
            map.put(i, map.getOrDefault(i,0)+1);
        }

        //对次数,即value进行排序,装到list集合中
        List<Map.Entry<Integer,Integer>> list=new ArrayList<>();
        list.addAll(map.entrySet());  
        Collections.sort(list,(o1, o2)->o2.getValue()-o1.getValue());

        //取list前k个就是出现频率最高的元素
        int[] result = new int[k];
        for(int i = 0; i < k; i++){
            result[i] = list.get(i).getKey();
        }

        return result;
    }
}

        但是时间不怎么理想就是说。。执行用时:13 ms, 在所有 Java 提交中击败了45.95%的用户

        题解是用了堆的思想,始终维护一个含k个元素的小顶堆,如果元素比堆顶元素小直接舍弃,因为此时该元素肯定不是出现次数前k的元素;如果元素比堆顶元素大,则加入小顶堆,同时堆顶元素出堆。

        堆在java中可以用PriorityQueue类(优先队列)实现,其中构建小顶堆不需要重写Comparator接口的compare方法,如果要构建大顶堆,则需要在compare中返回参数2-参数1的值。

//小顶堆
Queue<>() q1 = new PriorityQueue<>(); 
//大顶堆
Queue<>() q2 = new PriorityQueue<>((x, y) -> (y - x)); 
//同上
Queue<Integer> q2 = new PriorityQueue<>(k, new Comparator<Integer>() {
		@Override
		public int compare(Integer a, Integer b){
			return b - a;	//大顶堆:参数2-参数1;小顶堆:参数1-参数2
		}
	});

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值