在处理滑动窗口问题中,窗口边界的移动逻辑是关键!该部分我们针对滑动窗口中高频考点,继续梳理窗口边界的移动逻辑,从而游刃有余的面对接下来的挑战!此外,该部分还针对最长子串问题、异位词问题和最小子数组问题分别总结了模板以加深理解!
1.最长子串专题
最长字串问题特别喜欢考察滑动窗口思想,如:在给定的字符串S中,找到不含重复字符的最长字串长度。针对该问题,我们还可以继续扩展考察:至多包含2个不同字符的最长字串长度和至多包含K个不同字符的最长字串长度!接下来,针对最长字串这一专题,我们详细分析滑动窗口的边界问题,总结滑动窗口解题模板!
1.1无重复字符的最长字串
题目见LeetCode3,描述为:给定字符串s,找到不含重复字符的最长字串长度。
最长子串的模板:我们使用map来记录当前已经访问的字符串和它们的下标,即{key=字符,value=下标}。
题目分析:首先,创建窗口边界left、right,right不断遍历新元素,left不断调整窗口位置。当right遍历的元素存在于map中,说明下一时刻出现了重复字符,我们需要加入当前遍历的元素right,同时调整窗口边界left的值。
关键在于窗口边界left如何调整?答:取发生重复元素的下一个位置,比如abcefcc当right再次遍历到c时,子串出现重复字符,原来的子串abcef长度已经记录,因此需要更换成新的子(efc)继续向后寻找无重复字符的最长字串,其逻辑就是left = map.get(s.charAt(right))+1。
特例情况:当abba,第二次访问b时,窗口边界left应该变成2,第二次访问a时,窗口边界left会发生“回退”变成1,因此left = Math.max(map.get(s.charAt(right))+1, left)。
结合代码,继续理解上述分析!
public static int myLengthOfLongestSubstring(String s) {
if(s.length() == 0 || s == null) return 0;
int left = 0;
int max = 0;
HashMap<Character, Integer> map = new HashMap<>();
for (int right = 0; right < s.length(); right++) {
//发生重复
if(map.containsKey(s.charAt(right))){
left = Math.max(map.get(s.charAt(right))+1, left);
}
//增加未出现字符或修改已出现字符
map.put(s.charAt(right), right);
max = Math.max(right - left + 1, max);
}
return max;
}
1.2至多包含两个不同字符的最长子串
题目见LeetCode159,描述为:找出至多包含两个不同字符的最长子串t,并返回长度。
题目分析:应用最长子串的模板,直接读取前两个字符,然后使用right继续遍历剩余的字符,当map的容量小于3时,可以将当前遍历字符加入map;当map的容量等于3时,map移除之前的一个元素且窗口边界left发生移动!
关键在于,移除哪一个元素?窗口边界如何移动?答:移除窗口边界最左侧的元素,具体来说就是获取窗口最左侧元素的索引,从map中删除最左侧元素,移动窗口边界left=index+1。
结合代码,继续理解上述分析!
public static int myLengthOfLongestSubstringTwoDistinct(String s) {
int left = 0, right = 0;
int maxLen = 2;
HashMap<Character, Integer> map = new HashMap<>();
if(s.length() < 3) return s.length();
while(right < s.length()){
if(map.size() < 3){
map.put(s.charAt(right), right++);
}
if(map.size() == 3){
//找到滑动窗口最左侧元素下标
int index = Collections.min(map.values());
map.remove(s.charAt(index));
left = Math.max(left, index + 1);
}
maxLen = Math.max(maxLen, right - left);
}
return maxLen;
}
1.3至多包含K个不同字符的最长子串
题目见LeetCode340,描述为:找出至多包含K个不同字符的最长子串t,并返回长度。
题目分析:可以说1.3就是1.2“照猫画虎”衍生出来的,那么我们的解决办法也是“依葫芦画瓢”,直接只需要将原来map大小判断由3改成K+1即可!直接上代码!
public static int myLengthOfLongestSubstringKDistinct(String s, int k) {
if(s.length() < k + 1) return s.length();
int left = 0, right = 0;
int maxLen = k;
HashMap<Character, Integer> map = new HashMap<>();
while(right < s.length()){
if(map.size() < k + 1){
map.put(s.charAt(right), right++);
}
if(map.size() == k+1){
int index = Collections.min(map.values());
map.remove(s.charAt(index));
left = index + 1;
}
maxLen = Math.max(right - left, maxLen);
}
return maxLen;
}
2.长度最小的子数组
题目见LeetCode209,描述为:给定一个含有n个正整数的数组和一个正整数target,找到该数组中满足和>=target的长度最小的连续子数组,并返回其长度。
题目分析:使用right遍历数组,left调整窗口边界。“队列法”模板:让元素不断入队,当入队元素和>=target时,记录队列容量,然后调整窗口边界left使窗口不断缩小,直到窗口内元素和小于target,再让新元素入队!厘清思路,直接上代码!
public static int myMinSubArrayLen(int target, int[] nums) {
int left = 0, right = 0;
int minLen = Integer.MAX_VALUE, sum = 0;
while(right < nums.length){
sum += nums[right++];
while(sum >= target){
minLen = Math.min(minLen, right - left);
sum -= nums[left++];
}
}
return minLen == Integer.MAX_VALUE ? 0 : minLen;
}
3.盛水最多的容器
题目见LeetCode209。
题目分析:从最大窗口开始,不断缩小窗口,计算何时容器盛水最多!
关键在于,如何缩小窗口?答:选取窗口边界较小的短板,窗口在较小短板处向内收缩!
原理:若窗口在较大短板处向内收缩,会使得新窗口的底边首先变小,然后新窗口的较小短板不变或变小,水槽的面积一定变小!若窗口在较小短板处向内收缩,虽然使得新窗口的底边首先变小,但新窗口的较小短板可能变大,水槽的面积可能变大!因此,窗口在较小短板处向内收缩!
厘清思路,直接上代码!
public static int maxArea(int[] height) {
int i = 0, j = height.length - 1, res = 0;
while (i < j) {
res = height[i] < height[j] ?
Math.max(res, (j - i) * height[i++]) :
Math.max(res, (j - i) * height[j--]);
}
return res;
}
4.寻找子串异位词
什么是异位词?如果两个字符串仅仅字母出现顺序不同,则这两个字符串互称“异位词”(考场不知道的话,心态首先输了一半!)
那么,如何判断两个词是否是异位词呢?答:排序一下就可以!(方法是可行的,但是排序的时间消耗难以保证)别灰心!异位词的判断模板不就来了嘛!
异位词的判断模板:创建两个长度为26的int数组,charArray[i]表示字符('a'+i)出现的次数,当两个int数组相等,则两个字符串就是异位词,判断数组相等可以使用Arrays.equals(nums1,nums2)方法!
4.1字符串的排列
题目见LeetCode567,描述为:给定两个字符串s1和s2,写一个函数判断s2是否包含s1的排列。
题目分析:就是判断s1是否是s2子串的异位词!应用模板,直接上代码!
public static boolean myCheckInclusion(String s1, String s2) {
int len1 = s1.length(), len2 = s2.length();
if(len1 > len2) return false;
int[] charArray1 = new int[26];
int[] charArray2 = new int[26];
for (int i = 0; i < len1; i++) {
charArray1[s1.charAt(i)-'a']++;
charArray2[s2.charAt(i)-'a']++;
}
if(Arrays.equals(charArray1, charArray2)) return true;
for (int right = len1; right < len2; right++) {
charArray2[s2.charAt(right)-'a']++;
int left = right - len1;
charArray2[s2.charAt(left)-'a']--;
if(Arrays.equals(charArray1,charArray2)) return true;
}
return false;
}
4.2找到字符串中所有的异位词
题目见LeetCode438,描述为:给定两个字符串s和p,找到s中所有p的异位词的子串,并返回这些子串的起始索引。
题目分析:4.2和4.1同根同源,只不过多了一步记录并返回子串起始索引的任务!那么,在判断出是异位词的时候,保存子串的其实索引即可!厘清思路,直接上代码!
public static List<Integer> myFindAnagrams(String s, String p) {
int sLen = s.length(), pLen = p.length();
ArrayList<Integer> res = new ArrayList<>();
if(s.length()<p.length()) return res;
int[] sArray = new int[26];
int[] pArray = new int[26];
for (int i = 0; i < pLen; i++) {
sArray[s.charAt(i)-'a']++;
pArray[p.charAt(i)-'a']++;
}
if(Arrays.equals(sArray,pArray)) res.add(0);
for (int right = pLen; right < sLen; right++) {
sArray[s.charAt(right)-'a']++;
int left = right - pLen;
sArray[s.charAt(left)-'a']--;
if(Arrays.equals(sArray, pArray)) res.add(left + 1);
}
return res;
}
OK,《算法通关村第十六关——滑动窗口白银挑战笔记》结束,喜欢的朋友三联加关注!关注鱼市带给你不一样的算法小感悟!(幻听)
再次,感谢鱼骨头教官的学习路线!鱼皮的宣传!小y的陪伴!ok,拜拜,第十六关第三幕见!