LeetCode 239 Sliding Window Maximum
思路
相当有趣的一道题,除了暴力外有三种解法
有序集合
使用multiset
,初始化前k个数,然后每次查找-删除-添加-取最大值
即可。C++里set multiset
是有序的,虽然不支持随机访问,但是指定greater
模板参数后就可以直接使用*set.begin()
获得最大值了。
双端队列
维护一个双端队列,该队列总是保持如下特性
- 队列中任一位置前的元素总比该位置的大
- 队列最前端元素是最大值
- 队列最大为k
上述特性能够保证在O(1)的时间内获得最大值,为保持这些特性,考察一个新元素时,需要如下操作
- 若队列长度大于k,则不断将队列前端出队直到长度为k
- 从队列尾部开始,若尾部元素小于等于当前元素
a[i]
则出队,直到遇到大于a[i]
的或队列为空 - 将
a[i]
入队
然后就可以每次获得最大值了
分块
将数据分块,每块k个元素,那么对于某个滑动窗口x, x+k-1
,该窗口最多只覆盖两个块。因此,只需求出每个块内,前缀最大值
和后缀最大值
,那么对于一个覆盖两个块的窗口x, x+k-1
,假设其覆盖的块为[x, y]
和[y+1, x+k-1]
,则该窗口的最大值是 max ( max(x~y), max(y+1, x+k-1) )
,即前一个块的后缀最大值
和后一个块的前缀最大值
。
代码
class Solution {
public:
vector<int> maxSlidingWindow(vector<int> &nums, int k) {
// 分块
if (k == 0 || nums.empty() || nums.size() < k)
return {};
const int sz = nums.size();
vector<int> res(sz - k + 1, 0), lKMax(sz, 0), rKMax(sz, 0);
for (int i = 0; i < sz; i++) {
if (i % k == 0)
lKMax[i] = nums[i];
else
lKMax[i] = max(lKMax[i - 1], nums[i]);
}
rKMax[sz - 1] = nums[sz - 1];
for (int i = sz - 2; i >= 0; i--) {
if (i % k == 0)
rKMax[i] = nums[i];
else
rKMax[i] = max(rKMax[i + 1], nums[i]);
}
for (int i = 0; i < sz - k + 1; i++) {
res[i] = max(rKMax[i], lKMax[i + k - 1]);
}
return res;
}
vector<int> maxSlidingWindow1(vector<int> &nums, int k) {
// 队列法
if (k == 0 || nums.empty() || nums.size() < k)
return {};
const int sz = nums.size();
vector<int> res;
res.reserve(sz - k + 1);
deque<int> q;
for (int i = 0; i < sz; i++) {
while (!q.empty() && q.front() < i - k + 1)
q.pop_front();
while (!q.empty() && nums[q.back()] < nums[i])
q.pop_back();
q.push_back(i);
if (i >= k - 1)
res.push_back(nums[q.front()]);
}
return res;
}
};