算法圖解—最小覆蓋子串


【題目描述

給你一個字符串 s 、一個字符串 t 。返回 s 中涵蓋 t 所有字符的最小子串。如果 s 中不存在涵蓋 t 所有字符的子串,則返回空字符串 "" 。

注意:如果 s 中存在這樣的子串,我們保證它是唯一的答案。

示例 1:

輸入:s = "ADOBECODEBANC", t = "ABC"
輸出:"BANC"

示例 2:

輸入:s = "a", t = "a"
輸出:"a"

示例 3:

輸入:s = "a", t = "bb"
輸出:""
來源:力扣(LeetCode)第76題
鏈接:https://leetcode-cn.com/problems/minimum-window-substring
著作權歸領扣網絡所有。商業轉載請聯系官方授權,非商業轉載請注明出處。

熟悉的童靴都知道,這道題屬於雙指針的經典題目:

我們稱之為“滑動窗口”。

【題目分析

我們先看看題目是什么意思。

本問題要求我們返回字符串 s中包含字符串 的全部字符的最小窗口。

我們稱包含 t 的全部字母的窗口為「可行」窗口。

通過示例很容易明白題目意思,只要窗口里包含有目標字符串t中的左右字符,且要求是最短的。

那么什么是“滑動窗口”呢?

我把它比作家中的鋁合金推拉窗。

 

 

 對就是上圖的這個東東。

在滑動窗口類型的問題中都會有兩個指針。一個用於「延伸」現有窗口的 right 指針(右側),和一個用於「收縮」窗口的 left 指針(左側)。在任意時刻,只有一個指針運動,而另一個保持靜止。我們在 s 上滑動窗口,通過移動 right 指針不斷擴張窗口。當窗口包含 t 中全部所需的字符后,如果左側能收縮,我們就收縮窗口(左側指針  left)直到得到最小窗口。

有人會問,為什么收縮要從左側指針方向?

因為,你擴張是從右側的,當停止擴張時,你想想,為什么會停止擴張?是因為當窗口包含 t 中全部所需的字符了。所有此時最右側的字符一定是必須的,故要從左側縮減,如果能的話。

 

【圖解示例:參考leetCode】

作者:LeetCode-Solution
鏈接:https://leetcode-cn.com/problems/minimum-window-substring/solution/zui-xiao-fu-gai-zi-chuan-by-leetcode-solution/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

假設:

s  = ABAACBAB
t  =  ABC

第一步:

第二步:從第一步到第四步中間少了四步即是從A->B->A->A

第三步:left 縮減至B,仍然是包含了ABC,但不知道是否是最短,只好記錄下來left ,right,len = right - left。

第四步:繼續縮減;

第五步:再次包含ABC,記錄下此時的left ,right,len = len>(right - left) ? (right - left) : len。

第六步:仍然包含ABC,記錄下此時的left ,right,len = len>(right - left) ? (right - left) : len。left 繼續右移。

 

第七步:

第八步:同上,記錄,比較。不在贅述。

第九步:

第十步:至此后,結束。

 

 思路知道了,那么說一說細節:

1、如何判斷當前的窗口包含所有 t 所需的字符呢?

這涉及到數據結構的運用了,我們知道Java中hashMap查找的時間復雜度是O(1)。

借此,我們可以用一個需要匹配的哈希表 need<char, int> 表示 t 中所有的字符以及它們的個數,用一個窗口匹配哈希表 windows<char, int> 動態維護窗口中所有的字符以及它們的個數,如果這個動態表中包含 t 的哈希表中的所有字符,並且對應的個數都不小於 t 的哈希表中各個字符的個數,那么當前的窗口是「可行」的,即是滿足“包含”的條件的。

2、如何判斷這個動態表windows中是否包含 t 的哈希表中的所有字符,並且對應的個數都不小於 t 的哈希表中各個字符的個數?

可以利用一個int 變量即可,設為kind,我稱之為種類,即當windows中達到need中某字符的數量時,該變量加1。

【代碼實現】

//C++
class
Solution { public: string minWindow(string s, string t) { //if(s.size() < t.size()) return ""; unordered_map<char, int> need,windows; for(char c:t) need[c]++; int kind = 0;//windows中的種類 int left = 0; int right = 0; int len = INT_MAX;//返回長度 int start = 0;//返回起始下標 while(right < s.size()){ //current char char c = s[right++]; if(need.count(c)){ windows[c]++; if(windows[c] == need[c]){ kind++; } } //if windows中種類齊全了,縮減左側 while(kind == need.size()){ if(right - left < len){ start = left; len = right - left; } char d = s[left++]; if(need.count(d)){ windows[d]--; if(need[d] > windows[d]){ kind--; } } } } return len == INT_MAX ? "" : s.substr(start,len); } };

【Java】

class Solution {
    Map<Character, Integer> ori = new HashMap<Character, Integer>();
    Map<Character, Integer> cnt = new HashMap<Character, Integer>();

    public String minWindow(String s, String t) {
        int tLen = t.length();
        for (int i = 0; i < tLen; i++) {
            char c = t.charAt(i);
            ori.put(c, ori.getOrDefault(c, 0) + 1);
        }
        int l = 0, r = -1;
        int len = Integer.MAX_VALUE, ansL = -1, ansR = -1;
        int sLen = s.length();
        while (r < sLen) {
            ++r;
            if (r < sLen && ori.containsKey(s.charAt(r))) {
                cnt.put(s.charAt(r), cnt.getOrDefault(s.charAt(r), 0) + 1);
            }
            while (check() && l <= r) {
                if (r - l + 1 < len) {
                    len = r - l + 1;
                    ansL = l;
                    ansR = l + len;
                }
                if (ori.containsKey(s.charAt(l))) {
                    cnt.put(s.charAt(l), cnt.getOrDefault(s.charAt(l), 0) - 1);
                }
                ++l;
            }
        }
        return ansL == -1 ? "" : s.substring(ansL, ansR);
    }

    public boolean check() {
        Iterator iter = ori.entrySet().iterator(); 
        while (iter.hasNext()) { 
            Map.Entry entry = (Map.Entry) iter.next(); 
            Character key = (Character) entry.getKey(); 
            Integer val = (Integer) entry.getValue(); 
            if (cnt.getOrDefault(key, 0) < val) {
                return false;
            }
        } 
        return true;
    }
}

 

【復雜度分析】

  • 時間復雜度:最壞情況下左右指針對 s 的每個元素各遍歷一遍,哈希表中對 s 中的每個元素各插入、刪除一次,對 t 中的元素各插入一次。每次檢查是否可行會遍歷整個 t 的哈希表,哈希表的大小與字符集的大小有關,設字符集大小為 C,則漸進時間復雜度為 O(C⋅∣s∣+∣t∣)。
  • 空間復雜度:這里用了兩張哈希表作為輔助空間,每張哈希表最多不會存放超過字符集大小的鍵值對,我們設字符集大小為 C ,則漸進空間復雜度為 O(C)。

 

 

 

Over...

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM