使用滑動視窗思想查詢包含字元的最小覆蓋子串

給定一個字串S和字串T,要求找出S中包含T中所有字元的最小子串,不需要滿足順序,要求時間複雜度O(n)

如:

例子1:

String S =“XDOYEZODEYXNZ”;

String T =“XYZ” ;

返回 YXNZ

String S =“abcAbA”;

String T =“AA” ;

返回 AbA

傳統的方法是使用暴力解法,寫兩個雙層迴圈,但是這樣話,時間複雜度為 O(n2),n的平方了,而不是O(n)了

所以使用雙層迴圈肯定是不行了,這裡只能使用滑動視窗的思想來解決

滑動視窗的思想大概如下:

假設定義滑動視窗的左端為left、右端為right

1 先固定left,right=left,right右移動,並判斷字元是否包含在T中,包含則計數

2 當left和right之間剛好滿足包含T的所有字元時,找到了子串[left,right],但這個子串還不是最短的;

3 接下來就是縮短子串[left,right],改變left、right使當前子串縮短

因為是剛好找到滿足的條件,所以S[right]必定包含在T中,所以右端不能再變,也就是right不能變了,

只能移動left,由以下2種情況left是可以右移動的:

1)如果left對應的字元不包含在T中,說明該字元可以捨棄

2)如果left對應的字元包含在T中,但是該字串在子串中個數已經大於T中對應的字元的個數了,表明也可以捨棄,

比如子串中a有3個,T中a2個,那麼子串中減少一個a後,仍然滿足包含T的所有字元,所以這種情況也可以捨棄字元

使用while迴圈移動left,直到left對應的字元不滿足1)和2)為止,則此時是left和right就是當前的最小子串

4 比較當前的最小子串寬度是否大於前面找到的最小子串,如果大於,則使用第3步中的結果,否則捨棄第3步的結果

5 left右移一位,同時計數條件減1,重複 1、2、3、4的過程

因為此時的left的字元必定包含在T中,移動則當前子串丟失該字串,會導致包含條件不滿足了,所以計數條件要減一

單看我的描述可能理解會比較吃力,還是要看程式碼吧,裡面也有對應的註釋

程式碼如下:

import java。util。HashMap;import java。util。Map;/** * 使用滑動視窗思想 * 在S串中尋找包含字串T的所有字元的最小子串 * * 例如: S =“XDOYEZODEYXNZ” T =“XYZ”T=“XYZ” 找 出的最短子串為“YXNZ” * @author ssj * */public class MinStringWindow { public static void main(String[] args) { String S =“XDOYEZODEYXNZ”; String T =“XYZ” ; // String S =“abcAbA”;// String T =“AA” ; String min = minWindow(S,T); System。out。println(min); } /** * 在S字串中查詢包含T所有字元的最小子串 * @param S 原串 * @param T 目標串 * @return */ public static String minWindow(String S, String T) { if(S==null||T==null||S。length() window = new HashMap<>();//統計T中的每個字元的個數 Map need = new HashMap<>();//S的子串中獲取到的T中的字元個數,用於動態記錄 int i=0; //統計T中每個字元的個數,放入map window中 while(iwindow。getOrDefault(c, 0)||window。get(c)==null&&left<=right) { //如果該在包含在T中,子串捨棄了該字元,當然need中對應該字元的個數也要減一 if(need。get(c)!=null) { need。put(c, need。getOrDefault(c, 0)-1); } left++; c=S。charAt(left); } //判斷本次的子串是否比上一次的子串長度小,小則,更新滑動視窗的寬度和起始索引 if(width>right-left+1&&left<=right) { width=right-left+1; minStart=left; } //找下一個滿足的滑動視窗區間,left前進一位 if(left0&&need。getOrDefault(c, 0)<=window。getOrDefault(c, 0)) { //跳過的是包含的字元,此時減少一個字元,條件就不滿足了,所以count要減一 need。put(c, need。getOrDefault(c, 0)-1); count——; } left++; } } } right++; } return width==Integer。MAX_VALUE?null:S。substring(minStart, minStart+width); }}

執行結果:

YXNZ

使用滑動視窗思想查詢包含字元的最小覆蓋子串