(轉)leetcode:Find All Anagrams in a String 滑動視窗方法總結

weixin_34041003發表於2018-06-12

今天做了幾道滑動視窗的題,稍微總結一下。
起因源於早上在leetcode上pick one,隨機到了一個easy的題目,想著隨便做了,結果半天也找不到最優解,耗時300多ms,A是A了,不過就是暴力罷了。
題目是:Find All Anagrams in a String,連結在https://leetcode.com/problems/find-all-anagrams-in-a-string/ ,題目就不過多解釋了。
採用的方法是滑動視窗的方法,可以說這基本是一類方法,leetcode上有幾道題都可以用相同的思想,有點類似動態規劃,在大致思想步驟一致的前提下,每個題有各自不同的“條件式”。


滑動視窗思想

滑動視窗,就是利用雙指標技巧,以及map資料結構,維護一個不斷擴充套件、伸縮的視窗,在視窗內探測記錄我們感興趣的結果。比如這道題目,以用例分析:

Input:
s: “cbaebabacd” p: “abc”

Output:
[0, 6]

首先,先構造一個map,對於p中的每個字元char,都有map[char]++。
然後,初始化一個長度為0的視窗,left = 0,right = 0。第一步先擴充套件視窗,也就是在right的右邊界上做文章。每次right讀到一個字元char,都有map[char]–。當map[char]的值大於等於1時,很明顯就是視窗中進入了一個p中含有的字元。我們可以取一個變數count,值為p中所有字元的總數。每次有一個p中字元進入視窗,則count–。這樣,當count == 0的時候,表明我們的視窗中包含了p中的全部字元,得到一個結果。
當視窗包含一個結果以後,為了進一步遍歷,我們需要縮小視窗使視窗不再包含全部的p,同樣,如果map[char]>=0,表明一個在p中的字元就要移除視窗,那麼count ++,以此類推。
最終程式碼如下:

public class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        ArrayList<Integer> result = new ArrayList<>();
        if(s == null || p == null) return result;
        int left = 0,right =0,count = p.length();
        int[] map = new int[256];
        char[] sc = s.toCharArray();
        for (char c : p.toCharArray()) map[c] ++;
        while (right < s.length()) {
            if (map[sc[right++]]-->=1) count --;
            if (count == 0) result.add(left);
            if (right - left == p.length() && map[sc[left++]]++ >=0) count++;
        }
        return result;
    }
}

方法小結

可以說滑動視窗這種思想,關鍵點在於:
1、map中儲存值的意義
2、視窗什麼時候擴充套件和收縮,對應於left和right值什麼時候發生變化。
在解題的時候,首先嚐試擴充套件視窗right,看看什麼時候包含了一個結果,記錄結果。然後縮小左邊界left,直到視窗不在包含一個可能解!接著就可以繼續擴充套件視窗了,以此類推。

由此,可以得到一個模板:

public class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        ArrayList<Integer> result = new ArrayList<>();
        if(s == null || p == null) return result;
        int left = 0,right =0,count = p.length();

        int[] map = new int[256];
        char[] sc = s.toCharArray();
        //初始化map
        for (char c : p.toCharArray()) map[c] ++;
        while (right < s.length()) {
            //1:擴充套件視窗,視窗中包含一個T中子元素,count--;
            //2:通過count或其他限定值,得到一個可能解。
            //3:只要視窗中有可能解,那麼縮小視窗直到不包含可能解。         
        }
        return result;
    }
}

比如leetcode另一個題目 Minimum Window Substring:https://leetcode.com/problems/minimum-window-substring/

用例是:
S = “ADOBECODEBANC”
T = “ABC”
Minimum window is “BANC”.

瞭解了第一道題目以後,這道題目也很容易思考出來。解題時,按照步驟:

  1. 擴充套件視窗,視窗中包含一個T中子元素,count–;
  2. 通過count或其他限定值,得到一個可能解。
  3. 只要視窗中有可能解,那麼縮小視窗直到不包含可能解。

首先,維護一個map,一個視窗。先看右邊界,當視窗擴充套件包含全部ABC時停下,這個時候必然有count == 0。但是,這個時候的結果字串可能很長,所以我們要接著縮小左邊界。同時,當count == 0時,我們要一直縮小左邊界以找到更短的字串。慢慢count>0了,表明視窗中不包含全部的T了,那麼又要擴充套件視窗。依次類推,最終找到最短字串。
套用模板,程式碼如下:

public class Solution {
    public String minWindow(String s, String t) {
        int[] map = new int[256];
        int left = 0,right = 0,count = t.length(), minLen = Integer.MAX_VALUE;
        String result = "";
        for (char tc : t.toCharArray()) map[tc]++;
        char[] sc = s.toCharArray();
        while (right < s.length()|| count ==0) {
            if (count == 0){
                if (right-left+1<minLen){
                    minLen = right - left +1;
                    result = s.substring(left,right);
                }
                if (map[sc[left++]]++>=0) count++;
            }else {
                if ( map[sc[right++]]-->=1) count--;
            }

        }
        return result;
    }
}

https://leetcode.com/problems/longest-repeating-character-replacement/
也是一道滑動視窗的題,在解決諸如substring這類問題時,不妨嘗試想一想滑動視窗。

相關文章