代码随想录算法训练营第三期day13-栈与队列03

目录

1.T239:滑动窗口最大值

思路

代码实现

法1、自定义单调队列

法2、用元素索引代替元素

2.T347:前K个高频元素

代码实现

大顶堆

小顶堆


1.T239:滑动窗口最大值

T:给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 。

提示:

  • 1 <= nums.length <= 105

  • -104 <= nums[i] <= 104

  • 1 <= k <= nums.length

进阶:

你能在线性时间复杂度内解决此题吗?

S:

思路

个人认为本题应该是代码随想录刷题主线(不算“其他题目”)至今最难的一题

        如果考虑每次都怎么在滑动窗口中求局部最大值,那样时间复杂度绝对超出线性,目测O(n*k)起步

首先,可以明确的是:滑动窗口每次移动,都必定会有1个数进窗,有1个数出窗;

        从这一角度出发,我们能不能使用以栈或队列数据结构实现的容器,使得每次弹出的数都是局部最大值?

        很明显,没有现成的,但是可以自己实现!

代码实现

法1、自定义单调队列

class MyQue {
    private Deque<Integer> queue = new ArrayDeque<>();

    // 看一下当前要弹出的数值,是否等于队列出口元素的数值,相等才弹出
    public void poll(int value) {
        if (!queue.isEmpty() && queue.getFirst() == value) {
            queue.poll();
        }
    }

    // 如果push的数值大于入口元素的数值,那么就将队列后端的数值一个一个地弹出,直到push的数值小于等于队列入口元素的数值
    // 这样就保证了队列里的数值是(从队头到队尾)单调非递增的
    public void add(int value) {
        while (!queue.isEmpty() && value > queue.getLast()) {
            queue.removeLast();
            // queue.poll();
        }
        queue.add(value);//addLast
    }
        
    // 查询当前队列里的最大值 直接返回队列前端也就是front就行
    public int peek() {
        return queue.getFirst();//等效于peek
    }
}

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums == null || nums.length <= 1) return nums;
        MyQue myque = new MyQue();
        int[] res = new int[nums.length - k + 1];//数组的长度
        // 先将前k的元素放进队列
        for (int i = 0; i < k; ++i) {
            myque.add(nums[i]);
        }
        int num = 0;
        res[num++] = myque.peek();//漏了
        for (int i = k; i < nums.length; ++i) {
            myque.poll(nums[i - k]);// 滑动窗口移除最前面元素(当然不是一定会移除)
            myque.add(nums[i]);
            res[num++] = myque.peek();
        }
        return res;   
    }
}

法2、用元素索引代替元素

    public int[] maxSlidingWindow(int[] nums, int k) {
        ArrayDeque<Integer> deque = new ArrayDeque<>();
        int n = nums.length;
        int[] res = new int[n - k + 1];
        int idx = 0;
        for(int i = 0; i < n; i++) {
            // 根据题意,i为nums下标,是要在窗口[i - k + 1, i]中选最大值,只要保证两点
            // 1.队列头结点需要在[i - k + 1, i]范围内,不符合则要弹出
            while(!deque.isEmpty() && deque.peek() < i - k + 1) {//🚩难点!~法1中比较队头元素是否等于要出窗的value
            // while(!deque.isEmpty() && nums[deque.peek()] == nums[i - k + 1]) {
                deque.poll();
            }
            // 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出
            while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
                deque.pollLast();
            }

            deque.offer(i);

            // 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行
            if(i >= k - 1){
                res[idx++] = nums[deque.peek()];
            }
        }
        return res;
    }

其实不管是法1还是法2,关键点就俩:

  1. 移除:滑动窗口移动的时候,检查一下被移出去的元素(或其索引)是否还在求最大值的考虑范围内(被移出去就意味着,已经没有可能是局部最大值),如果还在(在也只可能在队头),就移出去(就算还在,之前也肯定已经作为最大值被用过了),否则什么也不用做;
  2. 加入:元素(或其索引)进入队列的时候,将前面所有不如自己大的全部踢出去,从而实现队列中的元素(或队列中存放索引对应的元素)是从队头到队尾单调非递增的。

2.T347:前K个高频元素

T:给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

提示:

  • 1 <= nums.length <= 105
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

进阶:你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

S:介于时间复杂度的要求和暗示,应该是要用二叉树或类似结构。

堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。

本题就非常适合使用优先级队列来对部分频率进行排序。

代码实现

大顶堆

在Java中用大顶堆更加简单

    public int[] topKFrequent(int[] nums, int k) {
        if (nums == null || nums.length <= 1 || k < 1) return nums;
        
        Map<Integer, Integer> map = new HashMap<>();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);//这个方法好!
        }
        //用优先队列:加入队列会自动按规则排序
        PriorityQueue<int[]> queue = new PriorityQueue<>((pair1, pair2) -> pair2[1] - pair1[1]);
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            queue.add(new int[]{entry.getKey(), entry.getValue()});
        }
        int[] res = new int[k];
        for (int i = 0; i < k; ++i) {
            // res[i] = queue.poll();
            res[i] = queue.poll()[0];
        }
        return res;
    }

小顶堆

        既然题目中说“返回其中出现频率前 k 高的元素,顺序任意”,那么用小顶堆也完全没问题,只要队列中元素达到k个以后,每次新加元素的时候,看看要不要把小的踢出去就行~

        PriorityQueue<int[]> queue = new PriorityQueue<>((pair1, pair2) -> pair1[1] - pair2[1]);
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            if (queue.size() < k) {
                queue.add(new int[]{entry.getKey(), entry.getValue()});
            } else if (entry.getValue() > queue.peek()[1]) {
                queue.poll();
                queue.add(new int[]{entry.getKey(), entry.getValue()});
            }
        }

  • 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、付费专栏及课程。

余额充值