前言:一直以来,对于一个给定长度为 n n n 的数组,都不知道应该如何求任意两个下标间的最小值,今天刚好碰到了这道题,也算是给我上了一课😂
我们简化问题:对于长度为 k k k 的窗口,求任意窗口间的最小值。之后只需要把窗口的范围变为 [ 2 , n ] [2, n] [2,n] 即可。
方法一:基于最大堆
class Solution {
public:
using PII = pair<int, int>;
vector<int> minSlidingWindow(vector<int>& nums, int k) {
priority_queue<PII, vector<PII>, greater<PII> > pq;
int n = nums.size();
for(int i = 0; i < k; ++i){
pq.push({nums[i], i});
}
vector<int> ans;
ans.push_back(pq.top().first);
for(int i = k; i < n; ++i){
pq.push({nums[i], i});
while(pq.top().second <= i - k){
pq.pop();
}
ans.push_back(pq.top().first);
}
return ans;
}
};
对于这种 O ( n l o g n ) O(nlogn) O(nlogn) 复杂度的方法,要点就一个:对于堆内的元素,我们只关心 当前堆顶的元素 (即最小值) 所在下标是否超出了窗口的左边界。举个🌰让大家可以 结合代码 领悟一下:
- 对于数组 [ 2, 3, 1, 4, 5, 6 ],演变过程如下
窗口变化 | 堆内元素 ( { v a l , i n d e x } \{val, index\} {val,index}) |
---|---|
[ 2 3 1 ] 4 5 6 | [ {1, 2}, {2, 0}, {3, 1} ] |
2 [ 3 1 4 ] 5 6 | [ {1, 2}, {2, 0}, {3, 1}, {4, 3} ] |
2 3 [ 1 4 5 ] 6 | [ {1, 2}, {2, 0}, {3, 1}, {4, 3}, {5, 4} ] |
2 3 1 [ 4 5 6 ] | [ {3, 1}, {4, 3}, {5, 4} ] |
对于表格的最后一行,此时 i − k = = 2 i-k == 2 i−k==2,所以会连续 p o p ( ) pop() pop() 3 次, 这也是 while 循环存在的意义
方法二:单调队列
class Solution {
public:
vector<int> minSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
vector<int> ans;
deque<int> q;
for(int i = 0; i < n; ++i){
while(q.size() && nums[q.back()] > nums[i]){
q.pop_back();
}
q.push_back(i);
if(i >= k-1){ // 窗口即将开始滑动
ans.push_back(nums[q.front()]);
if(i-k+1 == q.front())
q.pop_front();
}
}
return ans;
}
};
- 单调队列的做法对于堆而言减少了数据结构内部的调整,所以时间复杂度仅为 O ( n ) O(n) O(n)
对于以上的代码,可真的是:妙蛙种子吃着妙脆角到了米奇妙妙屋,妙到家了!