以数组为载体完成的任务中,有一类问题以「滑动窗口」为标签。
例题:「力扣」第 3 题:无重复字符的最长子串
力扣相关题型 第76题,第209题,第424题,第438题,第567题。
方法一:暴力解法
一个最直接的思考方案是:
枚举这个字符串的所有子串;
对于每一个子串都判断一下这个子串是否有重复字符;
在从没有重复字符的所有子串中
public class Solution {
public int lengthOfLongestSubstring(String s) {
int len = s.length();
int maxLen = 1;
// 枚举到 len - 2 即可
for (int left = 0; left < len - 1; left++) {
for (int right = 0; right < len; right++) {
String subString = s.substring(left, right + 1);
// 如果 subString 不包含重复元素,记录 subString 的长度,并且维护 maxLen
}
}
return maxLen;
}
}
作者:liweiwei1419
链接:https://leetcode-cn.com/leetbook/read/learning-algorithms-with-leetcode/x12j0r/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
滑动窗口的优化。
public class Solution {
public int lengthOfLongestSubstring(String s) {
// 重复元素上一次出现的位置很重要
int len = s.length();
if (len < 2) {
return len;
}
// 当做哈希表使用。key:字符的 ASCII 值,value:最新下标,-1 表示当前字符在遍历的时候还未出现过
int[] window = new int[128];
for (int i = 0; i < 128; i++) {
window[i] = -1;
}
char[] charArray = s.toCharArray();
int res = 1;
int left = 0;
// [left, right) 没有重复元素
for (int right = 0; right < len; right++) {
if (window[charArray[right]] != -1) {
left = Math.max(left, window[charArray[right]] + 1);
}
window[charArray[right]] = right;
// 注意理解这里为什么是 + 1
res = Math.max(res, right - left + 1);
}
return res;
}
}
力扣 第76题,最小覆盖子串
做法思路 注意点:
1. 如何判断字符串中出现的所有t中所有字符呢?
开辟字符频数数组(创建字符频数数组,将字符的 ACSII 码作为数组的下标),用频数数组记录一下,每个字符中出现的字符个数。让右边界进来的时候,字符频数加 1,window[right]++;当左边界滑出的时候,字符频数减 1,window[left]--;
int[] window = new int[128]; //s字符串的滑动窗口频数数组
int[] pattern = new int[128];//t字符串的频数数组
2. right
变长刚好满足条件,left
变短到刚好不满足条件,然后 right
变长刚好满足条件,如此循环下去,直到 right
到达末尾。
3. 循环不变量[left,right),强语义:滑动过程中right左边的数都是系统看到过的数,且right-left刚好等于数组长度。
3. 定义distance变量
由于我们并不关心字母的顺序,因此我频们采用的是对比数数组的方式。
先对 T字符串 做频数统计,然后设置一个变量 distance 表示 T 中一共有多少个的相同的字母;
left 和 right 在动的时候,只对 T 中出现的字母做统计。
窗口中单个字符的长度等于t中的字符时,distance我们不再增加
4. 窗口移动时的判断条件
当right向右滑动,且window[s[right]]<pattern[s[right]]时,distance+1
当left向右滑动,且window[s[left]]==pattern[s[left]]时,distance-1;
注意:当right向有移动的时候,超过t数组中的个数的时候 distance不再加一,同理,当left向左移动的时候只有window[schar[left]严格等于pattern[schar[left]]时,distance--;
代码实现
public String minWindow(String s, String t) {
//根据注意点可以完成前提定义变量
char[] schar=s.toCharArray();
char[] tchar=t.toCharArray();
int slen=s.length();
int tlen=t.length();
if(slen==0||tlen==0||slen<tlen){
return "";
}
int begin=0;
int distance=0; //窗口中体重出现字符的个数 对应字符超出不做计算
int minlen=slen+1; //初始话为不可能存在的值
//窗口位于【left,right)
int left=0;
int right=0;
//窗口频数数组
int[] window=new int[128];
int[] patten=new int[128];
for(char c:tchar){
patten[c]++;
}
//运行工作 这一步right向右移动
while(right<slen){ //如果right<slen,则有right向右滑动的循环
// 扫到t中不存在的字符时,即t字符中扫到schar【right】字符数==0,
if(patten[schar[right]]==0){
right++; //则right+1;跳过下面继续循环
continue;
}
//扫到t中存在字符时,且判断条件满足
if(window[schar[right]]<patten[schar[right]]){
distance++;
}
window[schar[right]]++;
right++;
//在这个循环下左边界滑动条件:两个字符数组频数相同的时候
while(distance==tlen){
//minlen的取值
if(right-left<minlen){
minlen=right-left;
begin=left;
}
//扫到t中不存在的字符时,即t字符中扫到schar【right】字符数==0,
if(window[schar[left]]==0){
left++;
continue;
}
//当left向左移动的时候只有window[schar[left]严格等于pattern[schar[left
//]]时,distance--;
if(window[schar[left]]==patten[schar[left]]){
distance--;
}
window[schar[left]]--;
left++;
}
}
if (minlen==slen+1){
return "";
}else {
return s.substring(begin,begin+minlen);
}
}