给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。
进阶:
你能在线性时间复杂度内解决此题吗?
示例:
输入: 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
提示:
1 <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4
1 <= k <= nums.length
暴力求解:超时
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int maxnum=-10001;
vector<int> result;
int n= nums.size();
//1、如果k> = nums.size,
if(k>=n)
{
for(int i=0;i<n;i++)
{
if(nums[i]>=maxnum) maxnum=nums[i];
}
result.push_back(maxnum);
return result;
}
//2、如果k<nums.size
else
{
int left=0;
int right=0;
for(left = 0;left<=n-k;left++)
{
//更新左右边界
right=left+k-1;
//更新最小值
maxnum=-10001;
for(int x=left;x<=right;x++)
{
if(nums[x]>=maxnum) maxnum=nums[x];
}
result.push_back(maxnum);
}
return result;
}
}
};
优化思路:
可以想到暴力方法肯定是有时间浪费的,在滑窗移动的时候,滑窗内插入一个新值,消失了一个旧值,有k-1个值仍然保留着。
我们只需要比较一下新插入的值和旧的k-1个值中的最大值,将得到的最大值赋给结果数组就可以了。
一开始我想的是用两个容量为2的数组,分别存放第一大的数值和它的下标,第二大的数值和它的下标,但是在推导的时候发现一个问题:
错误代码,具体疑问见代码注释:
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int maxnum[2]={
-10001,-10001}; //存储当前滑窗内第一大和第二大的数值
int index[2]={
0,0}; //存储当前滑窗内第一大和第二大的数值的下标
vector<int> result;
int n= nums.size();
if(k*n==0) return result;
//1、如果k> = nums.size,
if(k>=n)
{
for(int i=0;i<n;i++)
{
if(nums[i]>=maxnum[0]) maxnum[0]=nums[i];
}
result.push_back(maxnum[0]);
return result;
}
//2、如果k<nums.size
else
{
int left=0;
int right=0;
//将前k个数进行装载
for(int i=0;i<k;i++)
{
//nums[i]比第一大还大,更新第一大
if(nums[i]>=maxnum[0])
{
maxnum[0]=nums[i]; //第一大装载完毕
index[0]=i;
}
//第二大的范围:大于等于旧的第二大,小于等于新的第一大,并且不能是第一大的数
if(nums[i]>=maxnum[1] && nums[i]<=maxnum[0] && index[0]!=i)
{
maxnum[1]=nums[i]; //第二大装载完毕
index[1]=i;
}
}
left=1;
//从第k+1个数开始
for(right = k;right<n;right++,left++)
{
//如果移动的时候将最大值抛弃
if(index[0]<left)
{
//比较第二大的值和新插入的值的大小,获取最大值
//如果第二大值比新插入值大,则第二大值变为最大值,但是新插入的值不一定是第二大值
//所以我们需要构建一个从大到小排列的双端队列!
if(maxnum[1]>=)
}
result.push_back(maxnum);
}
return result;
}
}
};
发现需要构建一个从大到小排列的双端队列;
1、新的数入队列,并按照其大小排列,然后将比它小的数全部出队列(因为我们需要的是最大值,只要队列中有数比新入队列的数要小就说明它们绝对不可能是最大值,最大值最起码也是大于等于新入队列的数)。
2、观察队首(数值最大)的索引值是否在[left,right]之间,如果不在就出队列,直到队首索引值满足在[left,right]之间
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> result;
int n= nums.size();
if(n==0 ||k==0) return result;
if(k>=n)
{
auto maxPosition = max_element(nums.begin(), nums.end());
result.push_back(*maxPosition);
return result;
}
//2、如果k<nums.size
else
{
int left=0;
int right=0;
// 双向队列 保存当前窗口最大值的数组位置 保证队列中数组位置的数值按从大到小排序
deque< int> Dq;
// 遍历nums数组
for(int i = 0;i < n;i++){
// 保证从大到小 如果前面数小则需要依次弹出,直至满足要求
//如果队尾的元素小于Nums[i],队尾元素出队列
while(Dq.size() && nums[Dq.back()] <= nums[i]){
Dq.pop_back();
}
// 将当前数组下标入队列
Dq.push_back(i);
if(i>=k-1)
{
// 判断当前队列中队首是否有效,如果队首小于左边界,则将队首出队列,直到队首元素符合要求
while(Dq.size() && Dq.front() <= i-k){
Dq.pop_front();
}
result.push_back(nums[Dq.front()]);
}
}
return result;
}
}
};
虽然AC了,但是效率不高,不过基本思路是符合大众的:
官方给出的dp思路,还得理解理解
https://leetcode-cn.com/problems/sliding-window-maximum/submissions/
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
vector<int> output(n-k+1,0);
vector<int> left(n,0);
vector<int> right(n,0);
left[0]=nums[0];
right[n-1]=nums[n-1];
for(int i = 1;i<n;i++)
{
if(i%k == 0) left[i]=nums[i];
else left[i]=max(left[i-1],nums[i]);
int j = n-i-1;
if((j+1)%k == 0) right[j]=nums[j];
else right[j]=max(right[j+1],nums[j]);
}
for(int i = 0;i < n-k+1;i++)
output[i] = max(left[i+k-1],right[i]);
return output;
}
};
为什么:两数组一起可以提供两个块内元素的全部信息。
考虑从下标 i 到下标 j的滑动窗口。 根据定义,right[i] 是左侧块内的最大元素, left[j] 是右侧块内的最大元素。因此滑动窗口中的最大元素为 max(right[i], left[j])