1. 程式人生 > >關於使用滑動視窗解決陣列的一系列問題

關於使用滑動視窗解決陣列的一系列問題

在使用滑動視窗之前,我們需要知道什麼是滑動視窗,它又能幫助我們解決什麼樣的問題?

為了理解滑動視窗是什麼,我們先來看一個簡單的例子,難度指數:簡單

這道題在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]這個輔助陣列,大家可以好好體會一下,不懂的可以在下面提問,看到了會回答的。