🔥个人主页: Milestone-里程碑
❄️个人专栏: <<力扣hot100>> <<C++>><<Linux>>
🌟心向往之行必能至
给定一个整数数组 nums 和滑动窗口大小 k,窗口从数组最左侧移动到最右侧,返回每个窗口中的最大值。
核心思路:单调队列
暴力解法的时间复杂度是 \(O(nk)\),在数据量大时会超时。我们可以用单调队列将时间复杂度优化到 \(O(n)\)。
为什么选择单调队列
- 队列里存储的是数组下标,而非具体数值
- 队列中的下标对应的数组值是单调递减的
- 队首元素始终是当前窗口的最大值
代码逐行解析
cpp
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
vector<int> ans(n - k + 1); // 结果数组大小为窗口数量
deque<int> q; // 双端队列,存储下标
for (int i = 0; i < n; i++) {
// 1. 维护队列单调性:移除队尾所有小于当前元素的下标
while (!q.empty() && nums[q.back()] <= nums[i]) {
q.pop_back();
}
q.push_back(i);
// 2. 移除窗口外的队首元素
int left = i - k + 1; // 当前窗口的左边界
if (q.front() < left) {
q.pop_front();
}
// 3. 当窗口形成时,记录最大值
if (left >= 0) {
ans[left] = nums[q.front()];
}
}
return ans;
}
};
关键步骤拆解
-
维护队列单调性
- 新元素进入时,从队尾开始移除所有比它小的元素
- 这样可以保证队列里的元素始终是单调递减的
- 队首元素永远是当前窗口的最大值
-
移除窗口外的元素
- 每次移动窗口后,检查队首元素是否还在窗口范围内
- 如果队首元素的下标小于窗口左边界,就将其弹出
-
记录窗口最大值
- 当窗口左边界
left >= 0时,说明窗口已经形成 - 此时队首元素就是当前窗口的最大值,直接存入结果数组
- 当窗口左边界
复杂度分析
- 时间复杂度:\(O(n)\)。每个元素最多被加入和弹出队列各一次,总操作次数为 2n
- 空间复杂度 :\(O(k)\)。队列中最多存储
k个元素