【经典专题】毛毛虫模型——简单滑动窗口(Sliding Window)

312 阅读7分钟

什么是滑动窗口? 滑动窗口解决什么问题? 滑动窗口怎么分类? 滑动窗口的模板? 滑动窗口与双指针什么关系?

通过下面题目,最长的指定瑕疵度的元音子串,我们来总结一下 简单的滑动窗口的解法。我们先看看题目:

1、题目描述

题目名称:[最长的指定瑕疵度的元音子串]

定义:开头和结尾都是元音字母(aeiouAEIOU)的字符串为 元音字符串 ,其中混杂的非元音字母数量为其 瑕疵度 。比如:

  • “a” 、 “aa”是元音字符串,其瑕疵度都为0
  • “aiur”不是元音字符串(结尾不是元音字符)
  • “abira”是元音字符串,其瑕疵度为2

给定一个字符串,请找出指定瑕疵度的最长元音字符子串,并输出其长度,如果找不到满足条件的元音字符子串,输出0

子串:字符串中任意个连续的字符组成的子序列称为该字符串的子串。

解答要求时间限制:1000ms, 内存限制:256MB

输入 首行输入是一个整数,表示预期的瑕疵度flaw,取值范围 [0, 65535]。
接下来一行是一个仅由字符a-z和A-Z组成的字符串,字符串长度 (0, 65535]。

输出

输出为一个整数,代表满足条件的元音字符子串的长度。

样例

输入样例 1 

0
asdbuiodevauufgh

输出样例 1

3

提示样例 1

满足条件的最长元音字符子串有两个,分别为uioauu,长度为3。

输入样例 2

2
aeueo

输出样例 2

0

提示样例 2

没有满足条件的元音字符子串,输出0

输入样例 3 

1
aabeebuu

输出样例 3

5

提示样例 3

满足条件的最长元音字符子串有两个,分别为aabeeeebuu,长度为5

以上就是题目了,下面说说如何解题。

2、题解和说明

2.1、先贴代码(java实现):
private static int getLongestFlawedVowelSubstrLen(int flaw, String input) {
        int max = 0;
        int left = 0;
        int right = 0;
        int curFlaw = 0;
        char[] c = input.toCharArray();
        while (right < input.length()) {
            if (!isVowel(c[right])) {
                curFlaw++;
            }
            while (left < right && curFlaw > flaw) {
                if (!isVowel(c[left])) {
                    curFlaw--;
                }
                left++;
            }
            if (isVowel(c[left]) && isVowel(c[right]) && curFlaw == flaw) {
                max = Math.max(max, right - left + 1);
            }
            right++;
        }
        return max;
    }

    private static boolean isVowel(char ch) {
        return "aeiouAEIOU".indexOf(ch) != -1;
    }
2.2、写一个方法isVowel() 判断元音字母:
private static boolean isVowel(char ch) {
	return "aeiouAEIOU".indexOf(ch) != -1;
}
2.3、实现 getLongestFlawedVowelSubstrLen()

根据题意,子字符串求最值,想到双指针,滑动窗口思路,把该初始化的初始化,然后套框架根据条件取最值实现。

2.3.1、定义左右窗left、right,并初始化为0:

int left = 0;
int right = 0;

2.3.2、定义返回的最大值max,并初始化为0:

int max = 0;
char[] c = input.toCharArray(); // 顺便处理下input

2.3.3、套框架:

滑动窗口俩循环:外循环控制右窗,由right控制,内循环控制左窗,由left (和curFlaw)控制:

while (right < input.length()) { // 外循环,控制右窗
	//……(内循环)
	right++;
}
while (left < right && curFlaw > flaw) { // 内循环,控制左窗(条件curFlaw > flaw)
	left++;
}

2.3.4、根据条件,完善代码,更新最值结果。

// 外循环右移右窗口的过程中,遇到非元音字母,瑕疵度增加:right控制放在外循环。
if (!isVowel(c[right])) {
	curFlaw++;
}
// 内循环右移左窗口的过程中,遇到非元音字母,瑕疵度减少:left(和curFlaw)控制放在内循环。
if (!isVowel(c[left])) {
	curFlaw--;
}
if (isVowel(c[left]) && isVowel(c[right]) && curFlaw == flaw) {
	// 更新长度,刷最值:窗口为[left,right],所以长度:right - left + 1
	max = Math.max(max, right - left + 1);
}

2.3.5、最后,返回 max 就是我们要的结果了。

return max;

至此,题目就解答完了。该题很适合使用滑动窗口实现。

那么,回到前面问题,什么是滑动窗口?滑动窗口解决什么问题?滑动窗口怎么分类?滑动窗口的模板?滑动窗口与双指针什么关系?

3、滑动窗口总结

1、概念

滑动窗口是一类很常见的解题题型,最常见的就是子串问题了。因为滑动窗口是一个连续的,所以很容易出 最大串,最小串等最优解的问题。各种题型的不同点就是条件不同。因此,万变不离其宗,解好滑动窗口题型的关键就是分析条件了。

2、分类

滑动窗口分两类:A、固定长度窗口 、 B、可变长度窗口

A、固定长度窗口

窗口的大小是固定的,这是其实分为了窗口形成和窗口滑动两个过程,窗口形成就是要先让窗口达到要求的长度; 窗口滑动的过程在右边界长的时候,左边界也要跟着长,维持窗口长度不变;

B、可变长度窗口

这也是遇到最多的,控制窗口移动的不是长度,而是是否达到题目中某一条件,右边窗口一直在尝试移动,试图寻找到一个最值,然后可能会超出,这时候右边不动了,左边去动,左边得去移动重新让整个窗口满足题目中的条件。然后右边就又能移动了。接着跃跃欲试的去达到一个最值;

“毛毛虫”模型

滑动窗口就像毛毛虫在爬,右边脚先去动,动不了了左边脚去动,左边动不了,右边再动,就这样慢慢向前移; 最长子串:右边界不断移动寻找最优解,直至破坏了条件,左边界开始移动,然后满足条件,寻找可行解 最小子串:右边界不断移动寻找可行解,然后满足了条件,左边界开始移动,寻找最优解,直至破坏了条件,右边界再去移动重新使满足条件

while 还是 if

二者的关键区别是:

if(判断条件) {执行语句} :是单次执行判断。它与else结合形成分支结构语句。

while(判断条件){执行语句} :是循环执行判断,直到条件不满足。需要注意的是这里的判断条件是不是不断变化的?

那再通过边界移动、条件,你就很轻松知道选哪个了。

3.模板

3.1 最长模板:

左右指针(LR)在起始位置,R指针逐步向右循环滑动,在每次滑动的过程中,如果滑动窗口内的元素满足滑动窗口的需求,R指针不断向右滑动扩大窗口,并不断更新最优结果;如果窗内元素不满足条件,L指针不断向右缩小窗口,在此过程中并不断更新结果,直到R到达结尾

初始化 left,right,result,bestResult
while("右指针没有到结尾"){
    窗口扩大,加入right对应元素,更新当前result
    while("result不满足要求"){
        窗口缩小,移除left对应元素,left右移
    }
    更新最优结果bestResult
    right++;
}
返回bestResult

3.2 最短模板

左右指针(LR)初始在起始点,R逐步向右滑动,在每次滑动的过程中,如果窗口内的元素满足条件,L向右缩小窗口,并不断更新最小的结果,如果窗口内的元素不满足条件,R不断向右扩大窗口,直到R到达字符串的结尾

初始化 left,right,result,bestResult
while("右指针没有到结尾"){
    窗口扩大,加入right对应元素,更新当前result
    while("result满足要求"){
        更新最优结果bestResult
        窗口缩小,移除left对应元素,left右移
    }
    right++;
}
返回bestResult

4、本质

滑窗的本质是一个合规区间,既然是区间,就涉及到了所谓双指针。其实,根据双指针分类(相向、背向、同向),常见的是两个指针从整体的两侧边界向内收缩,这样的两个指针的方向是「相向而行」;而滑动窗口,区间不在于于收缩或者扩展而是在于移动,可以认为两个指针的方向是「同向而行」。

最后总结:子串类问题 + 最值问题 --> 果断采用滑动窗口吧