給你一個字符串 s 、一個字符串 t 。返回 s 中涵蓋 t 所有字符的最小子串。如果 s 中不存在涵蓋 t 所有字符的子串,則返回空字符串 "" 。
注意:
對於 t 中重複字符,我們尋找的子字符串中該字符數量必須不少於 t 中該字符數量。
如果 s 中存在這樣的子串,我們保證它是唯一的答案。
示例 1:
輸入:s = "ADOBECODEBANC", t = "ABC"
輸出:"BANC"
示例 2:
輸入:s = "a", t = "a"
輸出:"a"
示例 3:
輸入: s = "a", t = "aa"
輸出: ""
解釋: t 中兩個字符 'a' 均應包含在 s 的子串中,
因此沒有符合條件的子字符串,返回空字符串。
提示:
1 <= s.length, t.length <= 105
s 和 t 由英文字母組成
進階:你能設計一個在 o(n) 時間內解決此問題的算法嗎?
【分析】
雙指針方案。
首先:
(1)用valid表示目標字符窗中每一類字符的個數是否都滿足條件;
(2)用hash表need記錄目標串中每一個字符的個數;
(3)用hash表window記錄當前窗口中,包含的目標字符的個數;
(4)用length記錄當前window窗口的長度。
思路:
遍歷源字符串,利用雙指針,記錄窗口的位置,當窗口中每個目標字符的個數與目標字符串中對應字符的個數都相等時,就停止移動右側指針,開始縮小左側指針。
縮小左側指針時,不斷更新窗口長度,並更新窗口中每個目標字符的個數,當窗口中目標字符的個數與目標字符串中的對應字符的個數相等時,就減小有效長度,縮小窗口。
注意:
這裏,爲了避免邊界條件的討論,我們將雙指針的查找區間設置爲[left, right),即左閉右開的查找區間。
那麼,初始化查找區間就是[0, 0],最後一個查找區間就是[left, n)。
這種情況下初始查找區間[0, 0),窗口長度爲0,只要右側指針移動一步,窗口中就包含一個元素,同時,窗口的長度就是length = right - left,比較方便。
class Solution: def minWindow(self, s: str, t: str) -> str: need = dict() # 用hash表need記錄目標串中每一個字符的個數 window = dict() # 用hash表window記錄當前窗口中,包含的目標字符的個數 # 用need記錄所有目標字符的個數; 開始遍歷目標字符串 for _char in t: if _char not in need: # 若目標字符串中的字符不在need中,那麼need與window均將該字符數目記錄爲0 need[_char] = 0 window[_char] = 0 need[_char] += 1 # 跳出if條件語句,需要將遍歷到的char在need中進行+1記錄 left, right = 0, 0 # 用雙指針遍歷源字符串 valid = 0 # 記錄窗口中目標字符的每一類字符是否個數已經滿足,滿足就加1 start = 0 # 記錄窗口的右側位置 length = len(s) + 1 # 將窗口長度初始化爲一個無效值,即大於源字符串的長度值 # 右指針遍歷字符串的每一個位置:作爲窗口右側結束位置 while right < len(s): right_char = s[right] right += 1 # 增大窗口 if right_char in need: # 若窗口內的最後一個字符在目標字符串中, 更新窗口中的字符個數 window[right_char] += 1 if need[right_char] == window[right_char]: # 當前字符在窗口中的字符個數與目標字符串的字符個數相等,更新有效長度 valid += 1 # 當窗口中的每一類字符個數都與目標串中的每一類字符個數相等,就不斷縮小左側指針 while valid == len(need): # 更新最小的窗口長度 if right - left < length: start = left length = right - left first_char = s[left] left += 1 # 縮小窗口 # 若左側字符在目標字符裏面 if first_char in need: # 當前字符在窗口中的個數,與目標串個數相等,就減少有效長度 if window[first_char] == need[first_char]: valid -= 1 # 更新窗口中的字符個數 window[first_char] -= 1 return "" if length == len(s) + 1 else s[start:start + length]