在使用滑动窗口之前,我们需要知道什么是滑动窗口,它又能帮助我们解决什么样的问题?
为了理解滑动窗口是什么,我们先来看一个简单的例子,难度指数:简单
这道题在leetcode上也能找到:209 Mininum Size Subarray Sum
//难度:* /* 209 Minimum Size Subarray Sum 在字符串中找到满足条件的最小子字符串 给定一个数s和一个整形数组,找到数组中最短的一个连续子数组,使得连续子数组的数字和sum>=s,返回这个最短子数组的长度 */ public class MinimumSizeSubarraySum { //滑动窗口求最小子数组 //时间复杂度:O(n) //空间复杂度:O(1) public static int minSubArrayLen(int s, int[] arr){ int l = 0, r = -1;//arr[l...r]是我们的滑动窗口 int sum = 0;//子数组的和 int res = arr.length + 1;//子数组的最短长度
while( l < arr.length ){ if(sum < s && r+1 < arr.length){ r++; sum += arr[r]; } else{ sum -= arr[l]; l++; } if( sum >= s) res = Math.min(res, r-l+1); }if(res == arr. length+ 1) return 0; return res; } public static void main(String[] args) { int[] nums = { 2, 3, 1, 2, 4, 3}; int s = 7; System. out.println( minSubArrayLen(s , nums)); }}
这就是一个典型的滑动窗口的应用,arr[l..r]就是一个窗口,如果sum即子数组的和<s,则右边界++,将下一个数加入进窗口;如果sum>=s,则左边界++,将子数组第一个数踢出窗口。最后,每一次遍历的时,都判断一下子数组是否满足题目要求,即sum>=s,如果是,则将其与现在的res相比较,较小值存进res。
俺懒得画那啥图,没能体会其中思想的可以在草稿纸上画一下,多体会体会。
现在我们来看看下一个例子,难度:一般
//难度:** /* 3 Longest Substring Without Repeating Characters 在一个字符串中寻找没有重复字母的最长子字符串,A和a是不同的 */ public class LongestSubstring { //使用滑动窗口解决 /* 怎么判定有没有重复字符呢?使用一个freq[256]来记录字符出现的频率 */ public static int longestSubstring(String s){ int[] freq = new int[256];//初始化都是0,存储的是滑动窗口中的字符的频率 int l = 0, r = -1;//滑动窗口s[l...r] int res = 0;//滑动窗口的长度 while( l<s.length() ){ if( r + 1< s.length() && freq[s.charAt(r+1)] == 0 ){ r++; freq[s.charAt(r)]++; } else{ freq[s.charAt(l)]--; l++; } res = Math.max( res, r-l+1); } return res; } public static void main(String[] args) { String s = "abcabcdbb"; System.out.println(longestSubstring(s)); } }
可以看出,这道题和上一道思想上没有不同,都是创建一个滑动窗口来进行遍历,不过要注意的是这里使用的是一个freq[256]的整形数组来记录滑动窗口中字符的频率,而右边界r++,左边界l++的判断条件也不一样。可以总结出一个模板:
int l = 0, r = -1; while( l<s.length() ){ if(){ r++; }else{ l++; } }
创建一个滑动窗口,满足条件r++,否则l++;所以我们在这类大数组里找小数组的问题,都可以用这种模板,这时我们需要考虑的只是判断条件的不同。接下来,让我们看2个一样运用了滑动窗口的例子,这一次的例子比较难,我在这里先给出和两个题目,希望大家能独立完成,实在不行再来看下面的答案。
//难度:*** /* 438 Find All Anagrams in a String
给定一个字符串s和一个非空字符串p,找出p中的所有是s 的 anagrans字符串的子串,返回这些子串的起始索引 s = "cbaebabacd" p="abc" ,返回[0,6] s = "abab" p = "ba" , 返回[0,1,2] */
//难度:*** /* 76 Mininum Window Substring 给定一个字符串S,一个字符串T,在S中寻找最短的子串,包含T中所有的字符 S = "ADOBECODEBANC" T = "ABC" 结果为"BANC" */
第一个问题的答案:
public class FindinString { public static Integer[] FindinString(String s, String p){ int[] freq = new int[256];//存储p字符频率 for(int i=0;i<p.length();i++){ freq[p.charAt(i)]++; } int l = 0, r = -1; Vector<Integer> vec = new Vector<>(); while( l<s.length() ){ if( r+1<s.length() && freq[s.charAt(r+1)]!=0 ){ freq[s.charAt(r+1)]--; r++; }else{ freq[s.charAt(l)]++; l++; } if( r-l+1 == p.length()){//当滑动窗口的长度和字符串p的长度相等时就找到了 vec.add(l); } } Integer[] arr = vec.toArray(new Integer[vec.size()]); return arr; } public static void show(Integer[] arr){ System.out.print("["); for(int i=0;i<arr.length-1;i++) System.out.print(arr[i] + ","); System.out.print(arr[arr.length-1]+"]"); System.out.println(); } public static void main(String[] args) { String s = "cbaebabacd"; String p = "abc"; Integer[] arr = FindinString(s, p); show(arr); s = "abab"; p = "ab"; arr = FindinString(s , p); show(arr); } }
第二个问题的答案:
public class MininumWindowSubstring { public static String MininumWimow(String S, String T){ int[] freq = new int[256]; for( int i=0; i<T.length(); i++){ freq[T.charAt(i)]++; } String str = new String(); int l = 0, r = -1; int count = 0; int res = S.length()+1; while( l<S.length() ){ if( r+1<S.length() && count<T.length() ){ r++; if( freq[S.charAt(r)]>0) count++; freq[S.charAt(r)]--; }else{ if(freq[S.charAt(l)]>=0) count--; freq[S.charAt(l)]++; l++; } if( count == T.length() ){ if( r-l+1 < res ) str = S.substring(l , r+1); } } return str; } public static void main(String[] args) { String s = "ADOBECODEBANC"; String t = "ABC"; String str = MininumWimow( s, t); System.out.println(str); } }这里面的判断条件有不同的地方,但是模板还是一样的,都使用了freq[256]这个辅助数组,大家可以好好体会一下,不懂的可以在下面提问,看到了会回答的。