BM45 滑动窗口的最大值
知识点堆双指针队列
描述
给定一个长度为 n 的数组 nums 和滑动窗口的大小 size ,找出所有滑动窗口里数值的最大值。
例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
数据范围: ,数组中每个元素的值满足
要求:空间复杂度
,时间复杂度
示例1
输入:
[2,3,4,2,6,2,5,1],3
复制返回值:
[4,4,6,6,6,5]
复制
示例2
输入:
[9,10,9,-7,-3,8,2,-6],5
复制返回值:
[10,10,9,8]
复制
示例3
输入:
[1,2,3,4],3
复制返回值:
[3,4]
题解
一个滑动窗口实际上可以看做一个双端队列——将前端超出边界的元素出队,将后端小于当前元素的元素出队。实现步骤如下:
- 1. 假设滑动窗口的右边界为i,从i=0开始直到i=nums.size()-1结束
- 2. 使用双端队列存放滑动窗口元素中的坐标,条件如下:
- a.如果队列首端元素已经超过了当前滑动窗口的做边界,那么从队首出队直到元素处于滑动窗口中
- b.将滑动窗口的最后一个元素与双端队列的队尾元素比较大小,如果队列元素较小则一直从队尾出队直到队列为空或者遇到交大的数
- c. 将当前坐标放入队尾
- . 如果i >= size - 1,队首元素对应索引处的值就是当前滑动窗口中的最大值,原因:
- 队列由于已经将超过边界的元素剔除,因此队列中的元素都是当前滑动窗口中的索引
- 如果队列为空,则当前值就是最大值
- 如果队列不为空,则将队尾小于滑动窗口末尾值得元素从队尾出队,当出队完毕后队列要么为空,要么队首元素就是最大值
代码如下:
std::vector<int> maxInWindows(const std::vector<int> &nums, int size)
{
std::deque<int> dq;
std::vector<int> res;
for (int i = 0; i < nums.size(); ++i)
{
// i=size的时候,处理第二个滑动窗口,以此类推
// 窗口向前滑动一格,如果双端队列中最左端的索引已经不在队列中路,需要移出
while (!dq.empty() && dq.front() < (i - size + 1))
{
dq.pop_front();
}
// 此时队列如果不为空,则其索引肯定是在当前窗口中的,比较当前队列中的索引对应的值与当前值得大小,小于的全部出队
// 保证队列头的索引对应的值最大
while (!dq.empty() && (nums[dq.back()] < nums[i]))
{
dq.pop_back();
}
// 不管当前索引对应的值是否为该窗口中的最大值,都应该将该索引加入队列末尾
// 因为当前面的最大值出队后,当前索引可能是之后的其他窗口中的最大值
dq.push_back(i);
if (i + 1 >= size)
{
res.push_back(nums[dq.front()]);// dq.front一定是当前窗口的最大值
}
}
return res;
}
void test_case(const std::vector<std::pair<std::vector<int>, int>> &cases)
{
for (const auto &x : cases)
{
auto res = maxInWindows(x.first, x.second);
std::copy(res.begin(), res.end(), std::ostream_iterator<int>(std::cout, " "));
std::cout << std::endl;
}
}
int main()
{
std::vector<std::pair<std::vector<int>, int>> cases = {
{{10, 2, 3, 1, 4}, 3}};
test_case(cases);
return 0;
}