最小覆盖子串
描述
困难
给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。
示例
输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"
说明
如果 S 中不存这样的子串,则返回空字符串 “”。
如果 S 中存在这样的子串,我们保证它是唯一的答案。
解题
采用类似滑动窗口的想法
首先定义一个字典,用来保存t中的字符及对应个数
例如t=‘ABC’, t_dict={‘A’:1, ‘B’:1, ‘C’:1}
在s中定义一个滑动窗口,左右指针为left和right
定义最小的子串窗口,左右指针为min_left和min_right
当s的滑动窗口中出现了t中的字符时,t_dict中对应value减一
当t_dict中所有value不大于0时,说明t的字符全部出现在窗口中
value可以为负,说明在窗口中出现某字符的次数超过了t中的该字符的次数
为了判断t中字符是否已经全部出现,定义t_len
窗口滑动中出现了一个字符,并且在t_dict中的value大于0时,t_len减1
当t_len等于0时,说明t的字符全部出现在窗口中
遍历s时
- 当t_len > 0时,说明窗口中还没有包含所有t中的字符, right右移来扩大滑动窗口
- 当t_len == 0时,说明窗口中已包含所有t的字符,但可能同一字符出现了比t中次数还要多的次数,通过left右移来减小滑动窗口
from collections import defaultdict
class Solution:
def minWindow(self, s: str, t: str) -> str:
t_dict = defaultdict(int)
for char in t:
t_dict[char] += 1
t_len = len(t)
left = 0
min_left = 0
min_right = len(s)
for right, char in enumerate(s): # 这样写的话每次窗口右端right自动右移
if char in t_dict and t_dict[char] > 0:
t_len -= 1
if char in t_dict:
t_dict[char] -= 1
# 窗口中已包含所有字符,需要移动left找到最短窗口
if t_len == 0:
while True:
if s[left] not in t_dict: # 跳过非t中的字符
left += 1
elif t_dict[s[left]] < 0: # 跳过比t出现中更多次的多余字符
t_dict[s[left]] += 1
left += 1
else:
break
# 最小窗口
if right - left < min_right - min_left:
min_left, min_right = left, right
# 最小窗口的第一个字符肯定是t中的字符,left向后滑动
t_dict[s[left]] += 1
t_len += 1
left += 1
# 如果min_right没有发生变化,说明没有最小窗口,也就是无解
return '' if min_right == len(s) else s[min_left:min_right + 1]
if __name__ == "__main__":
S = "ADOBECODEBANC"
T = "ABC"
print(Solution().minWindow(S, T))