關於使用滑動視窗解決陣列的一系列問題
阿新 • • 發佈:2019-01-07
在使用滑動視窗之前,我們需要知道什麼是滑動視窗,它又能幫助我們解決什麼樣的問題?
為了理解滑動視窗是什麼,我們先來看一個簡單的例子,難度指數:簡單
這道題在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]; } elseif(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{ sum -= arr[l]; l++; } if( sum >= s) res = Math.min(res, r-l+1); }
這就是一個典型的滑動視窗的應用,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]這個輔助陣列,大家可以好好體會一下,不懂的可以在下面提問,看到了會回答的。