单调队列

单调队列往往用来解决滑动窗口最值问题。

目录

1. 239. 滑动窗口最大值

2.剑指 Offer 59 - II. 队列的最大值

 3. 862. 和至少为 K 的最短子数组


1. 239. 滑动窗口最大值

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

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

 

示例:

输入: 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
 

提示:

1 <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4
1 <= k <= nums.length

经典的使用单调队列求滑动窗口最值的问题。具体思路:

用单调队列维护 [i - k + 1...i]这个滑动窗口的信息,队头是这个滑动窗口的最大值。当遍历到 i 时,如果队列中有比 nums[i] 小的,应该全部pop,因为它们不可能成为包含 nums[i] 的滑动窗口的最大值。这样相当于维护了一个单调递减队列。

class Solution {
public:
    vector maxSlidingWindow(vector& nums, int k) {
        vector ans;
        deque dq;
        for(int i = 0; i < nums.size(); i++)
        {
            if(!dq.empty() && dq.front() <= i - k) dq.pop_front();
            while(!dq.empty() && nums[dq.back()] < nums[i]) dq.pop_back();
            dq.push_back(i);
            if(i >= k - 1) ans.push_back(nums[dq.front()]);
        }
        return ans;
    }
};

2.剑指 Offer 59 - II. 队列的最大值

请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。

若队列为空,pop_front 和 max_value 需要返回 -1

由于队列是先进先出的,每次push相当于滑动窗口右边扩展一格,每次pop相当于滑动窗口左边收缩一格,因此也可以转化成滑动窗口最值问题。具体思路:

用单调队列维护当前queue的信息,队头是当前queue的最大值。每次push value,相当于滑动窗口右边扩展一格,那么应当把单调队列中所有小于当前值的元素pop,因为它们不可能成为包含value的窗口最大值,还是相当于维护了单调递减队列。每次pop,需要检查pop的值是否是当前队列的最大值,如果是,那么单调队列也要同步pop。

class MaxQueue {
private:
    deque dq;
    queue q;
public:
    MaxQueue() {

    }

    int max_value() {
        if(q.empty()) return -1;
        return dq.front();
    }

    void push_back(int value) {
        q.push(value);
        while(!dq.empty() && value > dq.back()) dq.pop_back();
        dq.push_back(value);
    }

    int pop_front() {
        if(q.empty()) return -1;
        int t = q.front();
        q.pop();
        if(t == dq.front()) dq.pop_front();
        return t;
    }
};

 3. 862. 和至少为 K 的最短子数组

返回 A 的最短的非空连续子数组的长度,该子数组的和至少为 K 。

如果没有和至少为 K 的非空子数组,返回 -1 。

 

示例 1:

输入:A = [1], K = 1
输出:1
示例 2:

输入:A = [1,2], K = 4
输出:-1
示例 3:

输入:A = [2,-1,2], K = 3
输出:3
 

提示:

1 <= A.length <= 50000
-10 ^ 5 <= A[i] <= 10 ^ 5
1 <= K <= 10 ^ 9

这一题思维量比较大,可以用单调队列去做,也可以用单调栈去做。单调栈的方法记录在https://blog.csdn.net/chch1996/article/details/106245682#D.4%C2%A0862.%20%E5%92%8C%E8%87%B3%E5%B0%91%E4%B8%BA%20K%20%E7%9A%84%E6%9C%80%E7%9F%AD%E5%AD%90%E6%95%B0%E7%BB%84

首先,涉及到子数组求和的问题,无一例外都要利用前缀和来优化时间复杂度,并且一个常见套路就是一边遍历一边维护之前的前缀和信息,在当前值和之前的前缀和信息里获取结果。

得到前缀和数组后,思考题目要求的值有什么特点:

求子数组的和 >= K,那么如果当前遍历到 presum[i],要找的就是满足 j < i, presum[j] <= presum[i] - K 的最大的 j。这样的i j 的组合满足条件,要求的是所有这样组合中i j 的差值最小的。在这之后,要把 presum[i] 的信息放到之前维护的前缀和信息里,并且,之前所有大于等于 presum[i] 的值都应该被舍弃,因为它们与后面的值的组合不可能优于 presum[i]。

怎么结合单调队列来维护[0...i-1] 的前缀和信息呢? 如果维护一个单调递增的队列,只要队头满足 value <= presum[i] - K,那么就可以 pop,因为它们与后面的组合长度一定是大于与 i 的组合的长度的,也就是说它们形成的最优解一定是与 i 来组合。在求出 i 能形成的所有最佳组合之后,要把 i 放到队列里,此时,所有存在于队列中的,值大于等于 presum[i] 的都可以被pop,因为它们与后面的值的组合不可能优于 i 与后面的值的组合。

class Solution {
public:
    int shortestSubarray(vector& A, int K) {
        int n = A.size();
        int presum[50001] = {0};
        for(int i = 0; i < n; i++) presum[i+1] = presum[i] + A[i];
        // 对每一个 presum[i], 找[0...i-1]中,满足presum[j] <= presum[i] - K 的最大的 j 值
        // 用单调队列维护presum[0...i-1]的信息,队头是最小值所在的下标
        // 如果队头满足 presum[j] <= presum[i] - K, 那么它可以被pop,因为它与后面的presum[i]组成的长度只会更长
        // 如果当前值比队列中的值要小,可以把队列中比当前值大的值全部pop,因为当前值与后面的i可能组成的数组长度比它们都要小
        deque dq;
        dq.push_back(0);
        int ans = INT32_MAX;
        for(int i = 1; i <= n; i++)
        {
            while(!dq.empty() && presum[dq.front()] <= presum[i] - K)
            {
                ans = min(ans, i - dq.front());
                dq.pop_front();
            }
            while(!dq.empty() && presum[i] <= presum[dq.back()]) dq.pop_back();
            dq.push_back(i);
        }
        return ans < INT32_MAX ? ans : -1;
    }
};

 

你可能感兴趣的:(leetcode)