单调队列
- 经典用法:维持滑动窗口滑动过程中的最大值或最小值。最大值时,单调队列从头到尾降序
- 维持求解答案的可能性
- 单调队列里所有对象按照规定好的单调性组织
- 当某个对象从队尾进入单调队列时,会从队头或者队尾依次淘汰单调队列里,对后续求解答案没有帮助的对象
- 每个对象一旦弹出,可以结算其参与的答案,随后这个对象不再参与后续求解
#include <iostream>
#include <vector>
#include <deque>
using namespace std;
class Solution {
public:
vector<int> maxSlidingWindow(vector<int> &nums, int k) {
vector<int> res;
// 单调队列,队头到队尾递减,存的是下标
deque<int> dq;
// 先构造出长度 k 的窗口,并记录这个窗口的最大值
for (int right = 0; right < k; ++right) {
// 从队尾依次弹出小于等于的
while (!dq.empty() && nums[dq.back()] <= nums[right])
dq.pop_back();
dq.emplace_back(right);
}
res.emplace_back(nums[dq.front()]);
// 窗口:[left, right]
for (int right = k, left = 1; right < nums.size(); ++right, left++) {
// 从队尾依次弹出小于等于的
while (!dq.empty() && nums[dq.back()] <= nums[right])
dq.pop_back();
// 从队头依次弹出出界的
while (!dq.empty() && dq.front() < left)
dq.pop_front();
dq.emplace_back(right);
res.emplace_back(nums[dq.front()]);
}
return res;
}
};
#include <iostream>
#include <vector>
#include <deque>
using namespace std;
class Solution {
public:
bool withinLimit(vector<int> &nums, deque<int> &max_dq, deque<int> &min_dq, int number, int limit) {
int maxVal = max_dq.empty() ? number : max(number, nums[max_dq.front()]);
int minVal = min_dq.empty() ? number : min(number, nums[min_dq.front()]);
return maxVal - minVal <= limit;
}
int longestSubarray(vector<int> &nums, int limit) {
int res;
int len = nums.size();
deque<int> max_dq;
deque<int> min_dq;
for (int left = 0, right = 0; left < len; left++) {
// 窗口:[left, right),不超过限制才能加入窗口
while (right < len && withinLimit(nums, max_dq, min_dq, nums[right], limit)) {
// 加入单调队列
while (!max_dq.empty() && nums[max_dq.back()] <= nums[right])
max_dq.pop_back();
while (!min_dq.empty() && nums[min_dq.back()] >= nums[right])
min_dq.pop_back();
max_dq.emplace_back(right);
min_dq.emplace_back(right);
right++;
}
// 退出循环时,[left, right),就是从 left 右扩的最长子数组
res = max(res, right - left);
// 考虑下个左边界 left + 1 开始的窗口时,先移除将会越界的
if (max_dq.front() == left) max_dq.pop_front();
if (min_dq.front() == left) min_dq.pop_front();
}
return res;
}
};
#include <iostream>
#include <vector>
#include <deque>
#include <algorithm>
using namespace std;
bool cmp(pair<int, int> p1, pair<int, int> p2) {
return p1.first < p2.first;
}
bool ok(vector<pair<int, int>> &arr, deque<int> &maxDeque, deque<int> &minDeque, int limit) {
int maxVal = maxDeque.empty() ? 0 : arr[maxDeque.front()].second;
int minVal = minDeque.empty() ? 0 : arr[minDeque.front()].second;
return maxVal - minVal >= limit;
}
int main() {
// 录入数据
int len, limit;
cin >> len >> limit;
vector<pair<int, int>> arr;
for (int i = 0, index, val; i < len; ++i) {
cin >> index >> val;
arr.emplace_back(make_pair(index, val));
}
// 按下标升序排序
sort(begin(arr), end(arr), cmp);
deque<int> maxDeque;
deque<int> minDeque;
int res = 0x7fffffff;
for (int left = 0, right = 0; left < len; ++left) {
while (right < len && !ok(arr, maxDeque, minDeque, limit)) {
while (!maxDeque.empty() && arr[maxDeque.back()].second <= arr[right].second)
maxDeque.pop_back();
while (!minDeque.empty() && arr[minDeque.back()].second >= arr[right].second)
minDeque.pop_back();
maxDeque.emplace_back(right);
minDeque.emplace_back(right);
right++;
}
if (ok(arr, maxDeque, minDeque, limit))
res = min(res, arr[right - 1].first - arr[left].first);
if (!maxDeque.empty() && maxDeque.front() == left) maxDeque.pop_front();
if (!minDeque.empty() && minDeque.front() == left) minDeque.pop_front();
}
res = res == 0x7fffffff ? -1 : res;
cout << res;
}
#include <iostream>
#include <vector>
#include <deque>
#include <algorithm>
using namespace std;
class Solution {
public:
int shortestSubarray(vector<int> &nums, int k) {
int len = nums.size() + 1;
// sum[i] 表示前 i 个数的前缀和
vector<long> sum(len, 0);
for (int i = 1; i < len; ++i)
sum[i] = sum[i - 1] + nums[i - 1];
// 递增的单调队列
deque<int> min_dq;
int res = INT_MAX;
// 处理前 i 个数的前缀和,判断以 i 结尾,min_dq.front() 开头的一段子数组累加和是否达标
for (int i = 0; i < len; ++i) {
while (!min_dq.empty() && sum[i] - sum[min_dq.front()] >= k) {
// 达标就更新结果,然后把滑动窗口的左边界右移
res = min(res, i - min_dq.front());
min_dq.pop_front();
}
// 前 i 个数的前缀和加入单调队列
// 先弹出违背递增的元素
while (!min_dq.empty() && sum[min_dq.back()] >= sum[i])
min_dq.pop_back();
// 再加入
min_dq.emplace_back(i);
}
return res == INT_MAX ? -1 : res;
}
};
#include <iostream>
#include <vector>
#include <deque>
#include <algorithm>
using namespace std;
class Solution {
public:
// 横坐标递增,求在 x[j] - x[i] <= k 的条件下,x[j] - x[i] + y[i] + y[j] 的最大值
int findMaxValueOfEquation(vector<vector<int>> &points, int k) {
// 单调递减队列排序依据是 x[j] - x[i],存的是坐标
deque<pair<int, int>> maxDq;
int len = points.size();
int res = INT_MIN;
for (int i = 0, x, y; i < len; ++i) {
x = points[i][0];
y = points[i][1];
// x 的距离超过 k
while (!maxDq.empty() && (x - maxDq.front().first > k))
maxDq.pop_front();
if (!maxDq.empty())
res = max(res, maxDq.front().second + y + x - maxDq.front().first);
// 入队,淘汰违背递减的元素
while (!maxDq.empty() && maxDq.back().second - maxDq.back().first <= y - x)
maxDq.pop_back();
maxDq.emplace_back(make_pair(x, y));
}
return res;
}
};
#include <iostream>
#include <vector>
#include <deque>
#include <algorithm>
using namespace std;
class Solution {
public:
bool judge(vector<int> &tasks, vector<int> &workers, int pills, int strength,
int tl, int tr, int wl, int wr) {
deque<int> minDq;
// 已经使用的药量
int cost = 0;
// i 为工人编号,j 为任务编号
for (int i = wl, j = tl; i <= wr; ++i) {
// 当前工人初始能力能完成的任务全都入队
while (j <= tr && tasks[j] <= workers[i]) {
minDq.emplace_back(j);
j++;
}
if (!minDq.empty() && tasks[minDq.front()] <= workers[i]) {
// 没吃药时从队列里选任务量最少的任务完成
minDq.pop_front();
} else {
// 完成不了,就吃药,并且把吃药后能完成的任务全都入队
while (j <= tr && tasks[j] <= workers[i] + strength) {
minDq.emplace_back(j);
j++;
}
if (minDq.empty()) {
// 吃了药都做不了一个任务,那这些任务必定不能全部完成
// 因为有人啥也干不了,而任务和人数又是一样的
return false;
} else {
// 吃了药就选队列里选任务量最多的任务完成,充分利用药的加成
// 因为后面的人可能吃不到药,完成不了那么多任务量的任务
minDq.pop_back();
// 消耗一个药丸
cost++;
}
}
}
// 是否超出药丸总数
return cost <= pills;
}
int maxTaskAssign(vector<int> &tasks, vector<int> &workers, int pills, int strength) {
sort(begin(tasks), end(tasks));
sort(begin(workers), end(workers));
int tLen = tasks.size();
int wLen = workers.size();
int left = 0;
int right = min(tLen, wLen);
int mid;
// 右边界
while (left <= right) {
// 人数等于工作数等于 mid
mid = left + ((right - left) >> 1);
// 最少工作量的一组工作,交给工作能力最强的一群工人
if (judge(tasks, workers, pills, strength,
0, mid - 1, wLen - mid, wLen - 1))
left = mid + 1;
else
right = mid - 1;
}
return right;
}
};