栈与队列——6.力扣题目:239. 滑动窗口最大值

题目链接

解析:(单调队列)

题目要求:

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

大体思路:

​ 使用暴力法的时间复杂度是O(mn) ,如何使得时间复杂度为O(n)?

​ 使用单调队列,将滑动窗口中的值,放入单调队列中,使得队头元素最大,从队头到队尾一次单调递减。

​ 这样每次去滑动窗口对应队列中的队首元素,即可作为当前滑动窗口的最大值。

面临问题:

​ 问题①:如果对单调队列进行排序操作,时间复杂度还是O(mn),如何实现一个不需要排序的单调队列?

​ 每次add()元素的时候:

​ 如果队尾的元素比它小,直接破坏队尾的元素。新的队尾如果还比它小,继续破坏,直到队尾元素比它大或者队里所有元素都被破坏殆尽时,将这个元 素添加入队中。

​ 如果队尾元素比它大或者等于它,直接将该元素添加到队尾。(两者相等的时候是不能干掉的与它相等的队尾的,只能直接将它添加到队尾,不然问题 ②的操作会不知道队头元素是不是需要删除的那个元素,还是只是后面某个和需要删除的相等的元素。相等时添加了,那么②中删队头元素的时候,就不 会出现多删误删)

​ 经过这样的过程,可以一直保证,新来的元素足够大,就可以从队尾开始,逐个干掉队列中比它小的元素,直到遇到一个比它大元素,或者干掉了整 个队列位置。就能确保,从队尾到队头,元素依次递增。队头元素一定是最大的。

​ 问题②:在①的操作中,是实现了滑动窗口每一次添加元素的方式,如何保实现滑动窗口每一次滑动时,需要删除窗口前的元素?

​ 在①的操作中,其实滑动窗口是残缺的,因为很多元素被干掉了。那么滑动时需要删除的元素,也可能早在①的添加元素的操作中被干掉了。

​ 如果需要删除的元素早就被干掉了,那么这里删除操作什么也不要做。

​ 如果需要删除的元素没有被干掉,那么这里就要删除它。

​ 所以,如何判断这个元素是否已经被删除?如果没有被删除,如何找到它?

​ 滑动窗口要删除的元素,是这个窗口中最早进入队列的,队列先进先出,如果它没有被干掉,那一定就在队头。

​ 只需要比较队头的元素和需要删除的元素是否相等,

​ 相等,说明这个元素没有没有被干掉,删除队头元素即可。

​ 不相等,说明这个元素早就被干掉了,不需要任何操作

实现步骤

​ 步骤①:按照上述问题①和②的步骤,就可以自己定义一个MyQueue类,作为滑动窗口。

​ 步骤②:初始化第一个滑动窗口,将nums中前k个元素按照步骤①的添加步骤,进行添加。并记录最大值,也就是队头元素。

​ 步骤③:滑动窗口依次向后滑动,每一次执行步骤①中的删除操作,再执行添加操作,再记录最大值,也就是队头元素。

注意点:(LinkedList操作队尾的方法,getLast() ,removeLast())

虽然LinkedList的实现是基于双向链表,但是LinkedLsit类实现了Deque接口,可以把LinkedList当成Deque来用,所以Linkedlist就是队列

LinkedList操作队尾的方法:

​ getLast() 返回队尾元素,但是不删除对尾元素

​ removeLast() 返回队尾元素,并删除队尾元素

代码:
class MyQueue{
    LinkedList<Integer> queue = new LinkedList<>();
    public void MyQueue(){}
    // 删除元素,删除就好,不需要返回值。
    public void poll(int value) {
        // 这是在移动滑动窗口时,如果队头的和要删除的元素相等,才需要删除的
        // 队头元素和需要删除的元素不等,说明这里该删除的元素,在添加元素时它因为小于被添加的元素,早就被删除了。所以这里不需要任何操作。
        if (!queue.isEmpty() && value == queue.peek()) {
            queue.poll();
        }
    }
    // 添加元素
    public void add(int value) {
        // 为了确保队头位置是当前窗口内最大的元素,所以必须保证队列里的元素时单调增加的
        // 一旦添加的元素大于队尾元素,就破坏队尾元素,新的队尾元素如果还是小于添加的元素,继续破坏,直到遇到一个比它大的队尾元素,或者队列里的元素全被删除空了。
        while (!queue.isEmpty() && value > queue.getLast()) {
            queue.removeLast();
        }
        queue.add(value);
    }
    public int showMax() {
        return queue.peek();
    }
}



class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int[] arr = new int[nums.length - k + 1];
        MyQueue queue = new MyQueue();
        // 先将前k个元素,添加到队列(
        for (int i = 0; i < k; i++) {
            queue.add(nums[i]);
        }
        // 添加第一个滑动窗口的最大值
        arr[0] = queue.showMax();
        // 上述操作相当于初始化了第一个滑动窗口

        // 对后面的滑动窗口,从nums[k]开始,每滑动一次,实行出队和入队一次,再将最大值(队头元素)存入arr。
        for (int i = k; i < nums.length; i++) {
            queue.poll(nums[i - k]);
            queue.add(nums[i]);
            arr[i - k + 1] = queue.showMax();
        }
        return arr;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值