LeetCode239之滑动窗口最大值(相关话题:单调队列)

题目描述

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

返回每个滑动窗口中的最大值数组,时间复杂度要求O(n)

示数例 1:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置               最大值
------------------------------    -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7
示例 2:

输入:nums = [1], k = 1
输出:[1]
示例 3:

输入:nums = [1,-1], k = 1
输出:[1,-1]
示例 4:

输入:nums = [9,11], k = 2
输出:[11]
示例 5:

输入:nums = [4,-2], k = 2
输出:[4]

解题思路

为什么要用单调队列

对于每个滑动窗口,我们可以使用 O(k)的时间遍历其中的每一个元素,找出其中的最大值。对于长度为 n 的数组nums 而言,窗口的数量为 n-k+1,因此该算法的时间复杂度为O((n−k+1)k)=O(nk),会超出时间限制,因此我们需要进行一些优化。

引入一个单调递减的队列(左边对头数据最大,右边队尾数据最小)的目的是为了存储每次滑动窗口中最大值。

为什么用单调队列而不是单调栈

因为我们需要保持队列的两端都可以访问和修改。当新元素加入时,我们需要从队列后端移除所有比新元素小的元素,以保持队列的单调递减;同时,当窗口移动时,我们可能需要从队列前端移除旧的元素(即不再在当前窗口内的元素)。

 

详细的思路

1.想将我们第一个窗口的k个值存入单调双端队列中,单调队列里面的值为单调递减的。如果发现队尾元素小于等于要加入的元素,则将队尾元素出队,直到队尾元素大于新元素时,再让新元素入队,目的就是维护一个单调递减的队列。

2.我们将第一个窗口的所有值,按照单调队列的规则入队之后,因为队列为单调递减,所以队头元素必为当前窗口的最大值,则将队头元素添加到数组中。

3.然后把剩余的nums.length()-k个数按照规则进行入队,维护单调递减队列,同时剔除已经不在滑动窗口里的数据。

4.每次将队头元素存到返回数组里。

5.返回数组

编码实现

Java代码

class Solution {
    
    public int[] maxSlidingWindow(int[] nums, int k) {
		int len = nums.length;
		if (len == 0) {
			return nums;
		}
		int[] arr = new int[len - k + 1];
		int arr_index = 0;
		// 我们需要维护一个单调递增的双向队列
		Deque<Integer> deque = new LinkedList<>();
		// 先将第一个窗口的值按照规则入队
		for (int i = 0; i < k; i++) {
			while (!deque.isEmpty() && deque.peekLast() < nums[i]) {
				deque.removeLast();
			}
			deque.offerLast(nums[i]);
		}
		// 存到数组里,队头元素
		arr[arr_index++] = deque.peekFirst();
		// 移动窗口
		for (int j = k; j < len; j++) {

           //窗口的前一个(即不在窗口里的元素)元素等于队头元素
            if (nums[j - k] == deque.peekFirst()) {
                deque.removeFirst();
            }


			while (!deque.isEmpty() && deque.peekLast() < nums[j]) {
				deque.removeLast();
			}
			deque.offerLast(nums[j]);
			arr[arr_index++] = deque.peekFirst();
		}
		return arr;
	}
}

python代码

from collections import deque

# 为什么用单调队列而不是单调栈?因为我们需要保持队列的两端都可以访问和修改。当新元素加入时,我们需要从队列后端移除所有比新元素小的元素,以保持队列的单调递减;同时,当窗口移动时,我们可能需要从队列前端移除旧的元素(即不再在当前窗口内的元素)。

# 从队头到队尾保持单调递减

class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        
            if not nums:
                return []
            if k == 1:
                return nums

            window = deque()
            result = []

            for i, num in enumerate(nums):
                # 从队列尾部移除小于当前元素的所有元素
                # 这种方法保证了队列的头部始终是当前窗口的最大值。当新元素加入时,所有小于它的元素都不会影响后续窗口的最大值计算,因为这些较小的元素永远不会成为窗口的最大值(只要包含当前较大的元素)
                while window and nums[window[-1]] < num:
                    window.pop()

                # 将当前元素索引加入队尾
                window.append(i)

                # 口已经向右移动,当 window[0] 等于 i - k 时, 原来的之前窗口的最大值还霸占着队头就要移除掉
                if window[0] == i - k:
                    window.popleft()

                # 一旦窗口形成,添加最大值到结果列表
                if i >= k - 1:
                    result.append(nums[window[0]])

            return result

相似题目 

LeetCode209之长度最小的子数组(相关话题:滑动窗口,前缀和,二分)_int target = s + sums[i - 1];-CSDN博客

LeetCode076最小覆盖子串(相关话题:双指针,滑动窗口)_leetcode最小覆盖子串-CSDN博客

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数据与后端架构提升之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值