问题
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 k 内的数字。滑动窗口每次只向右移动一位。
返回滑动窗口最大值。在线性时间复杂度内解决此题
思路
- 首先定义一个双端队列(
滑动窗口记录数组的下标
),模拟滑动窗口,窗口内的元素始终保持从左到右的大小关系为大->小
,数据会从窗口的右边进入 - 当新进的元素大于或等于滑动窗口的最右元素,则将最右元素弹出,依次直到
最右元素大于该元素
或滑动窗口为空
,然后将该元素压入滑动窗口 - 然后,检查最左边元素是否符合要求(
是否过期
),如果过期,则将其弹出 - 将当前窗口(
已形成窗口
)的最左元素(最大元素
)添加到结果数组中
解法
#include <iostream>
#include <vector>
#include <deque>
#include <algorithm>
using namespace std;
vector<int> slipwindow(vector<int>& data, int k)
{
vector<int> ret;
if (data.size() == 0) return ret;
deque<int> q;
for (int i = 0; i < data.size(); i++)
{
while (!q.empty() && data[q.back()] < data[i])
{
q.pop_back();
}
q.push_back(i);
if (k == i - q.front())
{
q.pop_front();
}
if (i >= k - 1)
{
ret.push_back(data[q.front()]);
}
}
return ret;
}
int main()
{
int a[] = {1, 3, -1, -3, 5, 3, 6, 7};
vector<int> data(a, a + sizeof(a)/sizeof(int));
vector<int> ret = slipwindow(data, 3);
vector<int>::iterator it;
for(it = ret.begin(); it != ret.end(); it++)
{
cout << *it << "," ;
}
cout <<endl;
return 0;
}
扩展
滑动窗口法,可以用来解决一些查找满足一定条件的连续区间的性质(长度等)的问题。由于区间连续,因此当区间发生变化时,可以通过旧有的计算结果对搜索空间进行剪枝,这样便减少了重复计算,降低了时间复杂度。往往类似于“请找到满足xx的最x的区间(子串、子数组)的xx”这类问题都可以使用该方法进行解决。
初始化窗口端点L,R,一般L为0,R为1
初始化最优值
while R < len(Array):
while R < len(Array):
R += 1 #移动右端点
if R < len(Array):
更新状态
if 状态满足条件:
可选的更新最优值的位置
break #一旦满足条件即跳出
if R == len(Array): # 若循环是由于移动到数组末尾结束,则停止整个程序。因为之后已经不再有可能的解
break
while L < R:
更新状态 # 移动左端点,需要更新状态
L += 1
if 状态满足条件:
可选的更新最优值的位置
else: # 一旦窗口所在区间不再满足条件即跳出,去移动右端点
break
可选的对于L,R端点的后续处理
return 最优值
练习
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。
示例:
输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。
思路
- 滑动窗口的长度为0,位于数轴的最左端
- 滑动窗口右端R开始移动,直到区间满足给定的条件,也就是和大于7,停止于第三个元素2,记录下来当前的最优长度为4
- 滑动窗口左端L开始移动,并停止于第一个元素3,此时区间和为6,使得区间和不满足给定的条件
- 滑动窗口右端R继续移动,停止于第四个元素4,在过程中,最优长度仍然为4
- 滑动窗口左端L移动至第三个元素2,过程中更新最优长度为3
- 滑动窗口右端R移动至最后一个元素3,
- 滑动窗口左端L移动至最后一个元素,并在过程中更新最优长度为2
解法
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
#include <map>
using namespace std;
vector<int> subStringValue(vector<int>& data, int k)
{
map<int, vector<int> > ret;
if (data.size() == 0) return vector<int>();;
list<int> q;
int sum = 0, i = 0, minlen = data.size();
while (i <= minlen) // i++确保最后一个元素扫描完毕
{
while ( sum >= k) { //while 保证pop 第一个元素后,可能就相等了
if (sum == k && !q.empty()) // 相等,保持当前满足条件的一个值
{
int len = q.size();
list<int>::iterator it = q.begin();
while ( it != q.end())
{
ret[len].push_back(*it);
it++;
}
}
sum -= q.front(); // 大于,弹出第一个值
q.pop_front();
}
q.push_back(data[i]);
sum += data[i++];
}
return ret.begin()->second;
}
int main()
{
int a[] = {2,3,1,2,4,3,7,1,3,4,6,8}; //{1,3,-1,-3,-4,3,6,7,2,5,5};
vector<int> data(a, a + sizeof(a)/sizeof(int));
vector<int> ret = subStringValue(data, 7);
vector<int>::iterator it;
for(it = ret.begin(); it != ret.end(); it++)
{
cout << *it << "," ;
}
cout <<endl;
return 0;
}
考虑:如果给定的不是一串正整数,而是自然数?