一、滑动窗口的最大值
1.题目
Leetcode:第 239 题
给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例 1:
输入: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
示例 2:
输入:nums = [1], k = 1 输出:[1]
2.解题思路
1.一般解法(时间复杂度太高):遍历数组,找到所有的长度为k的窗口,并求出每个窗口的最大值。
2.使用单调队列:使用一个单调队列(从大到小)来维护滑动窗口中的最大值。在每次迭代中,它会弹出队列的第一个元素(如果它已经不在当前的滑动窗口内),然后将新的元素添加到队列中,并始终保持队列中的元素是当前窗口内的最大值。这样,在每次迭代结束时,队列的第一个元素就是当前滑动窗口的最大值,可以直接用于更新结果。
3.实现代码
#include <iostream>
#include <vector>
#include <deque>
#include <algorithm>
using namespace std;
//一般解法
class Solution1 {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
// 创建一个空的整数向量result,用于存储滑动窗口中的最大值。
vector<int> result;
// 使用for循环从数组的开始遍历到数组末尾减去窗口大小k的位置。
for (int i = 0; i <= nums.size() - k; i++) {
// 使用auto关键字和范围for循环结合max_element函数找到从索引i开始,长度为k的子数组中的最大元素。
auto max = max_element(nums.begin() + i, nums.begin() + i + k);
// 将找到的最大值添加到result向量的末尾。
// 注意:max是一个迭代器,所以使用*max来获取迭代器指向的元素的值。
result.push_back(*max);
}
// 函数返回包含每个滑动窗口最大值的向量result。
return result;
}
};
//使用单调队列
class Solution2 {
private:
// 定义一个名为MyQueue的内部类,用于实现一个单调队列(从大到小)
class MyQueue {
public:
// 使用deque(双端队列)来实现队列的基本操作
deque<int> que;
// 定义pop函数,用于从队列中弹出元素
// 当需要弹出的数值等于队列头部元素时,执行弹出操作
// 弹出之前会检查队列是否为空,以避免访问空队列
void pop(int value) {
if (!que.empty() && value == que.front()) {
que.pop_front(); // 如果条件满足,弹出队列头部元素
}
}
// 定义push函数,用于向队列中添加元素
// 当添加的元素(value)大于队列尾部元素时,先弹出队列尾部元素
// 这样可以保持队列中的元素始终是从大到小的单调顺序
void push(int value) {
while (!que.empty() && value > que.back()) {
que.pop_back(); // 弹出队列尾部元素,直到新元素可以插入
}
que.push_back(value); // 将新元素添加到队列尾部
}
// 定义front函数,用于获取队列头部元素的值
// 由于队列是单调递减的,所以头部元素是当前最大值
int front() {
return que.front(); // 返回队列头部元素
}
};
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k){
MyQueue que; // 创建一个MyQueue对象,用于处理滑动窗口
vector<int> result; // 创建一个vector,用于存储结果
// 循环处理数组的前k个元素,将它们添加到队列中
for (int i = 0; i < k; i++) {
que.push(nums[i]); // 将前k个元素依次添加到队列中
}
// 将队列中的最大值(队列头部元素)添加到结果vector中
result.push_back(que.front());
// 从数组的第k个元素开始,进行滑动操作
for (int i = k; i < nums.size(); i++) {
// 弹出队列中的最旧元素(已经不在当前滑动窗口中的元素)
que.pop(nums[i - k]);
// 将数组中的新元素添加到队列中
que.push(nums[i]);
// 将队列中的最大值(队列头部元素)添加到结果vector中
result.push_back(que.front());
}
// 返回结果vector,包含了每个滑动窗口的最大值
return result;
}
};
//测试
int main()
{
Solution1 s1;
Solution1 s2;
int k = 3;
vector<int>nums = { 1, 3, -1, -3, 5, 3, 6, 7 };
cout << "nums = ";
for (auto& i : nums)
{
cout << i << " ";
}
cout << endl;
vector<int>result1=s1.maxSlidingWindow(nums, k);
cout << "一般解法 result = ";
for (auto & i : result1)
{
cout << i << " ";
}
cout <<endl;
vector<int>result2 = s2.maxSlidingWindow(nums, k);
cout << "使用单调队列 result = ";
for (auto& i : result2)
{
cout << i << " ";
}
cout << endl;
cout << endl;
return 0;
}
ps:以上皆是本人在探索算法世界旅途中的浅薄见解,诚挚地希望得到各位的宝贵意见与悉心指导,若有不足或谬误之处,还请多多指教。