第一版:
思想:用双指针实现窗口,在每一个窗口找最大值。
如果就这么做出来了,这道题就不是困难了,考虑最坏的情况(比如所有的元素都一样,那么每次都需要初始化max,重新找最大值)时间复杂度O(n^2)
代码:
int main()
{
vector<int> nums = {1,3,1,2,0,5};
int k = 3;
int n = nums.size();
int start = 0;
int end = 0;
int max_ = INT_MIN;
vector<int> res;//int容器
while (end < n) {
max_ = max(max_, nums[end]);
if (end-start+1==k) {//满足窗口大小
res.push_back(max_);
if (max_==nums[start]) {//如果最大值在起始位置,初始化max_,重新找最大值
max_ = INT_MIN;
for (int i = start + 1;i <= end;i++) {
max_ = max(max_, nums[i]);
}
}
start++;
}
end++;
}
for (int i = 0;i < res.size();i++) {
cout << res[i] << endl;
}
}
注意:最大值在起始位置时,窗口滑动后需要重新找最大值,举个例子[5,1,2,3](后面所有例子k=3),第一个窗口max=5,滑动后,如果不重新找max,那么max=5依然比3大,得不到第二个窗口的最大值3,重新找就是在1,2中找到max=2。可能又要问了,为什么要重新找max=2呢?我直接重新初始化max=INT_MIN行不行呢?当然不行。因为我们是用的右指针end作为下标nums[end],换个例子[5,3,1,2],在第二个窗口时,我们只初始化max,end变成3指向第四个元素2,max_ = max(max_, nums[end]);看这条比较语句,发现在第二个窗口直接跳过了3,1直接和2比较了。
第二版:
思想:用一个双端队列来构成一个单调递减队列,队列的头部的元素最大,越往后越小。当向右移动窗口时,新的元素进入窗口,并与队列中的元素进行比较。如果它比队列尾部的元素大,则弹出队列尾部的元素,直到队列为空或新元素小于等于队列尾部的元素。这样可以确保队列头部的元素是窗口中的最大值。同时,如果队列头部的元素超出了窗口范围,也需要将其弹出。
总结:
-
对于当前元素,如果其下标与队列头部元素的下标之差等于窗口大小k,表示窗口需要滑动,队列头部元素不应该在当前窗口内,将其从队列头部弹出。例:[3,2,1,0]。ps:这个双端队列只是一个单调递减队列,它里面不一定就一直有窗口大小个数的元素,所以不要想着[1,2,3,0],双端队列弹出头部就把3弹出了,不是这样的,当遍历到2时,2比双端队列里的1大,这一步就会把1弹出。
-
对于每个元素,如果其值大于队列尾部元素的值,则从队列尾部依次弹出元素,直到队列尾部元素的值大于等于当前元素的值,再将当前元素的下标插入到队列尾部。
-
将当前元素的下标i添加到队列尾部。
-
对于每个元素,如果其下标大于等于k-1,即当前窗口大小为k(也可以说窗口开始滑动),将队列头部元素(即当前窗口内最大值的下标对应的元素)的值添加到结果数组中。
代码:
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
// vector<int> nums = { 1,3,-1,-3,5,3,6,7 };
// int k = 3;
int n = nums.size();
int start = 0;
int end = 0;
int max_ = INT_MIN;
vector<int> res;//int容器
deque<int> dq;
for (int i = 0; i < n; i++) {
if (!dq.empty() && dq.front() == i - k) {
dq.pop_front(); // 弹出队列头部的元素,如果超出了窗口范围
}
while (!dq.empty() && nums[dq.back()] < nums[i]) {
dq.pop_back(); // 弹出队列尾部的元素,直到新元素小于等于队列尾部的元素
}
dq.push_back(i); // 将新元素添加到队列尾部
if (i >= k - 1) {
res.push_back(nums[dq.front()]); // 队列头部的元素是当前窗口的最大值
}
}
return res;
}
};