单调队列的写法和单调栈一样,O(n),只是使用deque。

单调队列可取当前队列的最值。功能上类似优先队列,但多了删除功能,比如超出滑动窗口长度限制时做删除。用multiset能粗略模拟单调队列,O(nlgn)

滑动窗口的最大值

  • https://leetcode.com/problems/sliding-window-maximum/
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
	// 用deque实现单调队列,队头保存当前窗口最大值的下标
	// 递减队列 <=> 找下一个更大的数
	vector<int> ans;
	deque<int> dq;
	for (int i = 0; i < nums.size(); i++) {
		while (!dq.empty() && nums[i] > nums[dq.back()]) {
			dq.pop_back();
		}
		dq.push_back(i);
		if (i - dq[0] + 1 > k) dq.pop_front();  // 控制住窗口长

		if (i - k + 1 >= 0) ans.push_back(nums[dq[0]]);
	}
	return ans;
}

可取最大值的队列

class MaxQueue {
    queue<int> nums;
    deque<int> maxs; // 单调递减队列
public:
    void push(int x) {
        nums.push(x);
        while (!maxs.empty() && x > maxs.back())
            maxs.pop_back();
        maxs.push_back(x);
    }

    void pop() {
        if (nums.front() == maxs.front()) maxs.pop_front();
        nums.pop();
    }

    int getMax() {
        return maxs.front();
    }
};

滑动窗口最大最小值相差<=limit,求最长窗口长

  • https://leetcode.com/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/
int longestSubarray(vector<int>& nums, int limit) {
    // 子段max(A)-min(A)<=limit
    // 同时保留滑动窗口的最大最小值
    // 用deque实现单调队列,O(n)
    const int N = nums.size();
    int ans = 0, lo = 0;
    deque<int> maxq, minq;
    for (int hi = 0; hi < N; hi++) {
        while (!maxq.empty() && nums[hi] > maxq.back()) 
            maxq.pop_back();
        maxq.push_back(nums[hi]);
        
        while (!minq.empty() && nums[hi] < minq.back()) 
            minq.pop_back();
        minq.push_back(nums[hi]);
        
        while (maxq[0] - minq[0] > limit) {
            if (maxq[0] == nums[lo]) maxq.pop_front();
            if (minq[0] == nums[lo]) minq.pop_front();
            lo++;    
        }
        ans = max(ans, hi - lo + 1);
    }
    return ans;
}

或者用multiset

int longestSubarray(vector<int>& nums, int limit) {
    // 子段max(A)-min(A)<=limit
    // 同时保留滑动窗口的最大最小值
    // 用multiset,O(nlogn)
    const int N = nums.size();
    multiset<int> win;
    int ans = 0, lo = 0;
    for (int hi = 0; hi < N; hi++) {
        win.insert(nums[hi]);
        while (*win.rbegin() - *win.begin() > limit) {
            win.erase(win.find(nums[lo++]));
        }
        ans = max(ans, hi - lo + 1);
    }
    return ans;
}

有负数数组,和>=T的最短子段

参见同名笔记

子序列项间隔<=k,求最大子序列和

int constrainedSubsetSum(vector<int>& nums, int k) {
    // 设dp[i]表示以nums[i]结尾的nums[0..i]的最大子序列和,
    // dp[i] = nums[i] + max{ dp[i-k], ..., dp[i-2], dp[i-1], 0 }
    // 用可取最大值的单调递减队列q,保存dp前k项[i-k..i-1]
    // 初始dp[0]=nums[0]
    const int N = nums.size();
    vector<int> dp(N);
    dp[0] = nums[0];
    deque<int> q = {{dp[0]}};
    int ans = dp[0];
    
    for (int i = 1; i < N; i++) {
        if (i-k-1 >= 0 && q[0] == dp[i-k-1]) q.pop_front();
        dp[i] = nums[i] + max(q[0], 0);
        while (!q.empty() && dp[i] > q.back()) { // 递减队列
            q.pop_back();
        }
        q.push_back(dp[i]);

        ans = max(ans, dp[i]);
    }
    return ans;
}

对于一些点,xi<xj,最大化(yi-xi)+(yj+xj)

int findMaxValueOfEquation(vector<vector<int>>& points, int k) {
    // 因为xi<xj,要最大化(yi-xi)+(yj+xj)
    // 从左往右扫描,对于特定的j点,要最大化它前面点的yi-xi
    // 求[j-k..j)滑动窗口内的最大yi-xi值,用单调递减队列
    int ans = INT_MIN;
    deque<array<int, 2>> dq; // 保存{yi-xi, xi}
    for (const auto& p : points) {
        while (!dq.empty() && p[0] - dq[0][1] > k) { // xj-xi
            dq.pop_front();
        } 
        // 有效的k长窗口
        if (!dq.empty()) {
            ans = max(ans, dq[0][0] + p[1] + p[0]);
        }
        
        while (!dq.empty() && p[1] - p[0] > dq.back()[0]) {
            dq.pop_back();
        }
        dq.push_back({p[1] - p[0], p[0]});
    }
    return ans;
}