LeetCode 239. 滑动窗口最大值

LeetCode 239. 滑动窗口最大值

1、题目

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

示例 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]

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
  • 1 <= k <= nums.length

2、单调队列

思路

为了解决这个问题,我们可以使用双端队列(deque)来存储滑动窗口中的元素索引。双端队列的特点是可以在两端添加或删除元素,非常适合处理滑动窗口问题。
我们可以使用双端队列(deque)来实现一个单调递减队列,即队列的头部元素始终是窗口中的最大值。
设计单调队列:

  1. void pop(int val):如果队列不为空,并且 val 等于队列的头部元素,则弹出队列的头部元素。
  2. void push(int val):当插入一个新的元素 val 时,这个方法会删除队列中所有小于 val 的元素,以确保队列的头部始终是窗口中的最大值。然后,它将val添加到队列的尾部。
  3. int front():返回队列头部的元素,即当前窗口的最大值。

接下来使用这个单调递减队列来解决滑动窗口最大值问题。遍历数组,对于每个窗口,维护一个单调递减队列,队列的头部元素始终是窗口中的最大值。

  1. 首先遍历 nums 的前 k 个元素,并使用 que.push 方法将它们添加到队列中。这样,队列 que 就包含了 nums 的前 k 个元素,并且其头部是这些元素中的最大值。
  2. 接下来,它将队列头部的元素(即前 k 个元素的最大值)添加到 result 中。
  3. 然后,对于 nums 中的剩余元素(从索引k开始),它执行以下操作:
    • 使用 que.pop 方法删除窗口中的第一个元素(即 nums[i - k])。
    • 使用 que.push 方法将新元素 nums[i] 添加到队列中。
    • 将队列头部的元素(即当前窗口的最大值)添加到 result 中。
  4. 最后,函数返回结果 result

示例

假设nums = [1, 3, -1, -3, 5, 3, 6, 7],k = 3。

  • 初始窗口为[1, 3, -1],最大值为3。
  • 滑动窗口为[3, -1, -3],最大值为3。
  • 滑动窗口为[-1, -3, 5],最大值为5。
  • 滑动窗口为[-3, 5, 3],最大值为5。
  • 滑动窗口为[5, 3, 6],最大值为6。
  • 滑动窗口为[3, 6, 7],最大值为7。

因此,函数将返回[3, 3, 5, 5, 6, 7]。

代码

#include <iostream>
#include <vector>
#include <deque>

using namespace std;

class Solution {
private:
    //单调递减队列(从大到小),即队列的头部元素始终是窗口中的最大值。
    class MyQueue {
    public:
        // 使用 deque 来实现单调队列
        deque<int> que;

        // 如果队列不为空,并且 val 等于队列的头部元素,则弹出队列的头部元素
        void pop (int val) {
            if (!que.empty() && val == que.front()) {
                que.pop_front();
            }
        }

        // 向队列中添加元素 val,如果 val 大于队列的尾部元素,则弹出队列的尾部元素,直到 push 的数值小于等于队列入口元素的数值为止。
        // 这样就保持了队列里的数值是单调从大到小的了。        
        void push (int val) {
            while (!que.empty() && val > que.back()) {
                que.pop_back();
            }
            que.push_back(val);
        }

        // 返回队列头部的元素,即当前窗口的最大值。
        int front() {
            return que.front();
        }
    };

public:
    // 使用单调递减队列来解决滑动窗口最大值问题。
    // 遍历数组,对于每个窗口,维护一个单调递减队列,队列的头部元素始终是窗口中的最大值。
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue que;
        vector<int> result;
        
        // 遍历 nums 的前 k 个元素,并使用 que.push 方法将它们添加到队列中。这样,队列 que 就包含了 nums 的前 k 个元素,并且其头部是这些元素中的最大值。
        for (int i = 0; i < k; i++) {
            que.push(nums[i]);
        }

        // 将队列头部的元素(即前 k 个元素的最大值)添加到 result 中。
        result.push_back(que.front());

        // 遍历 nums 的后续元素
        for (int i = k; i < nums.size(); i++) {
            // 弹出当前窗口的第一个元素 nums[i - k]
            que.pop(nums[i - k]);
            // 将新元素 nums[i] 添加到队列中
            que.push(nums[i]);
            // 将当前窗口的最大值添加到 result 中
            result.push_back(que.front());
        }
        return result;
    }
};

int main() {
    Solution s;
    vector<int> nums = {1,3,-1,-3,5,3,6,7};
    int k = 3;
    vector<int> result = s.maxSlidingWindow(nums, k);
    for (int i = 0; i < result.size(); i++) {
        cout << result[i] << " ";
    }
    cout << endl;
    return 0;
}

复杂度分析

  • 时间复杂度: O(n)
  • 空间复杂度: O(k)

3、使用 multiset 作为单调队列

思路

使用 multiset 作为单调队列
多重集合 (multiset) 用于存储当前窗口中的元素。multiset 允许存储重复的元素,并且元素在容器中是有序的(默认按升序排列)。
在遍历原数组的时候,只需要把窗口的头元素加入到 multiset 中,然后把窗口的尾元素删除即可。因为 multiset 是有序的,并且提供了 *rbegin(),可以直接获取窗口最大值。

  • 通过 for 循环遍历 nums 中的每个元素。
  • if (i >= k) st.erase(st.find(nums[i - k])): 如果当前索引 i 大于或等于 k,则从 st 中删除 i - k 索引处的元素。这是为了确保 st 中只包含当前窗口内的元素。
  • st.insert(nums[i]): 将当前元素插入到 st 中。
  • if (i >= k - 1) ans.push_back(*st.rbegin()): 如果当前索引 i 大于或等于 k - 1,则将 st 中的最大元素(即 st.rbegin() 指向的元素)添加到 ans 中。这是因为当窗口中有 k 个元素时,我们可以确定窗口的最大值。

代码

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        // 创建一个 multiset 容器 st,用于存储滑动窗口中的元素
        multiset<int> st;
        // 创建一个 vector 容器 ans,用于存储每个滑动窗口的最大值
        vector<int> ans;
        // 遍历 nums 数组
        for (int i = 0; i < nums.size(); i++) {
            // 如果当前位置 i 大于等于 k,说明滑动窗口的大小已经达到 k,需要移除窗口最左边的元素
            if (i >= k) st.erase(st.find(nums[i - k]));
            // 将当前位置的元素插入到 multiset 容器中
            st.insert(nums[i]);
            // 如果当前位置 i 大于等于 k - 1,说明滑动窗口的大小已经达到 k - 1,可以计算窗口的最大值
            if (i >= k - 1) ans.push_back(*st.rbegin());
        }
        // 返回每个滑动窗口的最大值
        return ans;
    }
};

复杂度分析

  • 时间复杂度: O(n)
  • 空间复杂度: O(k)
  • 29
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用\[1\],可以使用暴力解法来求解滑动窗口最大值。具体的做法是,遍历数组,对于每个窗口,使用一个内部循环来找到窗口中的最大值,并将其存储在结果数组中。时间复杂度为O(n*k),其中n为数组长度,k为窗口大小。 根据引用\[2\],还可以使用队列来求解滑动窗口最大值。具体的做法是,使用一个双端队列来维护一个单调递减的窗口。遍历数组,对于每个元素,首先判断队头是否在滑动窗口范围内,如果不在,则将其从队头移除。然后,将当前元素与队尾元素比较,如果当前元素大于队尾元素,则将队尾元素移除,直到队列为空或者当前元素小于等于队尾元素。最后,将当前元素的索引插入队尾。如果滑动窗口的元素个数达到了k个,并且始终维持在窗口中,就将队头元素加入答案数组中。时间复杂度为O(n),其中n为数组长度。 综上所述,可以使用暴力解法或者使用队列来求解leetcode滑动窗口最大值。 #### 引用[.reference_title] - *1* *3* [leetcode239. 滑动窗口最大值](https://blog.csdn.net/kkkkuuga/article/details/124829581)[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^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Leetcode#239. 滑动窗口最大值 (Java解法)](https://blog.csdn.net/paranior/article/details/114890555)[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^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值