關於雙指標的方法,可能大家並不陌生,而滑動視窗演算法,其實算是雙指標的一種實現方式,主要用於解決子串問題。並且在leetCode上起碼有10道以上的滑動視窗應用題目,難度均在middle和hard。因此,我本文也致力於闡述自己的想法,供大家互相學習。
首先滑動視窗演算法,顧名思義,就是要維護一個視窗,然後通過不斷的滑動,最終跟新答案。下面是我從“Labuladuo演算法小抄”上看到的虛擬碼:
int left = 0, right = 0; while (right < s.size()) { // 增大視窗 window.add(s[right]); right++; while (window needs shrink) { // 縮小視窗 window.remove(s[left]); left++; } }
這個演算法的複雜度是O(N),比暴力演算法高效許多。基本思路也就是用左右指標去維護視窗,下面是我自己一般喜歡用的程式碼寫法:
public void SlidingWindow(String s,String t){ Map<Character,Integer> need=new HashMap<Character,Integer>(); //最終目的子串 Map<Character,Integer> Window=new HashMap<Character,Integer>(); //目前視窗集合 char [] tCharArray=t.toCharArray();
char [] sCharArray=s.toCharArray(); for(char c : sCharArray) need.put(c, need.getOrDefault(c, 0) + 1);
int left=0,right=0; int valid=0; while(right<s.length()) { char c=s.charAt(right); //c是將要移入視窗的字元 right++; //右移視窗 .... //對視窗中的資料進行更新 System.out.println("left為:"+left+" right為:"+right); while (window needs shrink)//判斷左側視窗是否要收縮 { char d=s.charAt(left); //d是將移出的視窗的字元 left++; //左移視窗 .... //進行視窗中資料系列更新 } }
看一道LeetCode上的hard題。
這道題暴力只是理論上可以解決的,但是,一定會超時。比如下面的寫法:
for (int i = 0; i < s.size(); i++) for (int j = i + 1; j < s.size(); j++) if s[i:j] 包含 t 的所有字母: 更新答案
而通過滑動視窗演算法,即可大大降低複雜度和運算時間:
public String minWindow(String s, String t) { Map<Character,Integer> need=new HashMap<>(); //子串視窗 Map<Character,Integer> window=new HashMap<>(); //滑動視窗 char [] sCharArrays=s.toCharArray(); char [] tCharArrays=t.toCharArray(); for(char c:tCharArrays){ need.put(c,need.getOrDefault(c,0)+1); //用hashmap記錄出現字母的次數 } int left=0,right=0; int valid=0; int length=Integer.MAX_VALUE; int start=0; while(right<s.length()) //右指標滑動 { char c=sCharArrays[right]; //right劃過視窗的元素 right++; if(need.containsKey(c)) //如果是是子串的組成字母之一,加入滑動視窗 { window.put(c,window.getOrDefault(c,0)+1); if(window.get(c).equals(need.get(c))) //如果該字母數目在滑動視窗和子串視窗中相同,valid++ valid++; } while(valid==need.size()) //當所有的字母在window和need中出現次數相同,開始收縮視窗 { if(right-left<length) { length=right-left; start=left; } char d=sCharArrays[left]; //left劃出的元素 left++; if(need.containsKey(d)){ if(window.get(d).equals(need.get(d))) //注意:在這裡只能寫.equals(),不能寫== valid--; // 因為這裡是Integer物件之間的比價,如果超過 window.put(d,window.get(d)-1); //-128-127會重新new一個物件使==報錯。(上面的equals同) } } } if(length==Integer.MAX_VALUE) return ""; return s.substring(start,start+length); }
最後,滑動視窗算是雙指標裡比較複雜的一種情況,但如果掌握大概思路,許多子串問題都能順利的解決。