什么是滑动窗口? 滑动窗口解决什么问题? 滑动窗口怎么分类? 滑动窗口的模板? 滑动窗口与双指针什么关系?
通过下面题目,最长的指定瑕疵度的元音子串,我们来总结一下 简单的滑动窗口的解法。我们先看看题目:
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
满足条件的最长元音字符子串有两个,分别为uio和auu,长度为3。
输入样例 2
2
aeueo
输出样例 2
0
提示样例 2
没有满足条件的元音字符子串,输出0
输入样例 3
1
aabeebuu
输出样例 3
5
提示样例 3
满足条件的最长元音字符子串有两个,分别为aabee和eebuu,长度为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、本质
滑窗的本质是一个合规区间,既然是区间,就涉及到了所谓双指针。其实,根据双指针分类(相向、背向、同向),常见的是两个指针从整体的两侧边界向内收缩,这样的两个指针的方向是「相向而行」;而滑动窗口,区间不在于于收缩或者扩展而是在于移动,可以认为两个指针的方向是「同向而行」。
最后总结:子串类问题 + 最值问题 --> 果断采用滑动窗口吧