滑动窗口算法

1. 概念

滑动窗口是一种基于双指针的一种思想,两个指针指向的元素之间形成一个窗口。

2.分类

窗口有两类,一种是固定大小类的窗口,一类是大小动态变化的窗口。

3.应用场景

利用滑动窗口获取平滑的数据,如一段连续时间的数据平均值,能够有更好的稳定性,如温度监测。

什么情况可以用滑动窗口来解决实际问题呢?

  • 一般给出的数据结构是数组或者字符串
  • 求取某个子串或者子序列最长最短等最值问题或者求某个目标值时
  • 该问题本身可以通过暴力求解

4.算法思想

  1. 在序列中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引闭区间 [left, right] 称为一个窗口。

滑动窗口 mysql order by 滑动窗口算法图解_窗口大小

  1. 先不断地增加 right 指针扩大窗口 [left, right],直到窗口中的序列符合要求。

滑动窗口 mysql order by 滑动窗口算法图解_滑动窗口 mysql order by_02

  1. 此时,停止增加 right,转而不断增加 left 指针缩小窗口 [left, right],直到窗口中的序列不再符合要求。同时,每次增加 left前,都要更新一轮结果。

滑动窗口 mysql order by 滑动窗口算法图解_滑动窗口 mysql order by_03

  1. 重复第 2 和第 3 步,直到 right 到达序列的尽头。
    思路其实很简单:第 2 步相当于在寻找一个可行解,然后第 3 步在优化这个可行解,最终找到最优解。左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动。

5.算法模板

(1)单循环--适用于固定窗口大小

def template():
# 初始化滑动窗口两端
left = right = 0

# 序列及序列长度
seq, seq_len = xx, xx

# 滑动窗口序列
slide_win = []

# 结果值
rst = xx

while right < seq_len:
    slide_win.append(seq[right])
    # 还没找到一个可行解
    if not avaliable(slide_win):
        # 扩大窗口
        right += 1
    else:
        # 找到一个可行解,更新结果值
        rst = update()
        # 缩小窗口
        left += 1

(2)双层循环 -- 适用于动态窗口

def template():
# 初始化滑动窗口两端
left = right = 0

# 序列及序列长度
seq, seq_len = xx, xx

# 滑动窗口序列
slide_win = []

# 结果值
rst = xx

while right < seq_len:
    slide_win.append(seq[right])
    # 还没找到一个可行解
    if not avaliable(slide_win):
        # 扩大窗口
        right += 1
        continue

    # 循环更新可行解
    while avaliable(slide_win):
        # 找到一个可行解,更新结果值
        rst = update()
        # 缩小窗口
        left += 1

6.实战代码

(1) 固定窗口 LC438_找到字符串中所有字母异位词

vector<int> findAnagrams(string s, string p) 
{
//窗口:字母和p相同的字串  窗口大小 p的长度
//当窗口小于n 则first++
//当窗口大于等于n  则second++

////异位词即字母出现相同

vector<int> ans=vector<int>(26,0);
vector<int> ver=vector<int>(26,0);
vector<int> res;
    for(int i = 0 ;i < p.size();i++)
    {
        ans[p[i]-'a']++;
    }
    for(int i = 0;i <s.size();i++ )
    {
        ver[s[i]-'a']++;
        if(i >= p.size()) ver[s[i-p.size()]-'a']--;
        if(ans == ver) res.push_back(i-p.size()+1);
    }
    return res;
}

(2) 动态窗口 LC76_最小覆盖子串

string minWindow(string s, string t)
{
//窗口:s字串的子串能覆盖住t  窗口大小 至少为t.length()
//当窗口小于 t.length() 则first++
//当窗口大于等于t.length()  若满足则second++

//覆盖即 s的字符出现数 >= p的字符出现数
//当使用unordermap 来存储<字符,字符出现次数>

string result;
if(s.empty()||t.empty()) return result;

unordered_map<char,int> map;
unordered_map<char,int> window;
for(char c:t)
{
    map[c]++;
}
int minlength = INT_MAX;
int lettercount =0;
for(int slow = 0 ,fast =0;fast < s.length();fast++)
{
    char c =s[fast];
    if(map.find(c) != map.end())//若此字符是目标串出现过的字符
    {
        window[c]++;
        if(window[c]<= map[c])
        lettercount++;//第一次遇到时 让count++
    }
    if(lettercount >= t.length())//全部的字符已经覆盖到了
    {
        while(map.find(s[slow])==map.end() || window[slow]> map[slow])
	//slow的字符没在目标串中出现的字母或者字符出现次数多与需要
	//在满足基恩本条件下缩小窗口
        {
            window[s[slow]]--;
            slow++;
        }
        if((fast - slow + 1 )< minlength)
        {
            minlength = fast-slow+1;
            result = s.substr(slow,minlength);
        }
    }
}
return result;
}