算法 - 滑动窗口最大值

问题

给定一个数组 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] 是该条件下的长度最小的连续子数组。

思路

  1. 滑动窗口的长度为0,位于数轴的最左端
  2. 滑动窗口右端R开始移动,直到区间满足给定的条件,也就是和大于7,停止于第三个元素2,记录下来当前的最优长度为4
  3. 滑动窗口左端L开始移动,并停止于第一个元素3,此时区间和为6,使得区间和不满足给定的条件
  4. 滑动窗口右端R继续移动,停止于第四个元素4,在过程中,最优长度仍然为4
  5. 滑动窗口左端L移动至第三个元素2,过程中更新最优长度为3
  6. 滑动窗口右端R移动至最后一个元素3,
  7. 滑动窗口左端L移动至最后一个元素,并在过程中更新最优长度为2

preview

解法

#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;
}

考虑:如果给定的不是一串正整数,而是自然数? 

 

  • 12
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值