问题来源
76 . 最小覆盖子串
给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。
示例:
输入: S = "ADOBECODEBANC" , T = "ABC"
输出: "BANC"
说明:
如果 S 中不存这样的子串,则返回空字符串 "" 。
如果 S 中存在这样的子串,我们保证它是唯一的答案。
大佬解析
代码
"""
需求:
给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。
带着一张采购清单 去买菜!!!
思想:
1.双指针+滑动窗口
滑动窗口的思想:
先寻求一个可行的解
在可行解的基础上,优化这个可行解
优化到不能优化,产生出一个可能的最优解
继续寻求新的可行解
继续优化这个可行解
……
在可能的最优解中产生出最优解
穷举是一根筋,滑动窗口法懂得做选择
每一个字符最多被遍历 2 次,即被左、右指针分别走过一次
但穷举法,有的节点被遍历次数显然超过 2
如果说穷举扫描是一根筋,那滑动窗口法,就懂得做选择,去掉了重复的工作
"""
class Solution :
def minWindow ( self, s: str , t: str ) - > str :
"""❤组织思路写代码
需要一个变量,控制窗口的扩张/收缩
窗口的扩张/收缩 取决于 —— 当前窗口是否找齐所有目标字符
引入 missingType 变量,表示:当前缺失的字符种类数(还要找齐几种字符)
遍历前,它的初始值为 t 字符串的字符种类数
当 missingType == 0 时,代表没有缺少种类,当前窗口找齐了所有目标字符
是否找齐了字符 取决于 各个字符的缺失情况
用一个 HashMap 存储各个目标字符,和它对应的缺失个数
t = 'baac' ,则 map 初始值为 { a: 2, b: 1, c: 1 }
map 中的值是 动态变化 的,比如窗口新纳入一个 a ,则 map['a'] 值 -1
map['a'] 为 0 代表不缺 a 字符了,找齐了
好比 带着一张采购清单 去买菜
想要买齐 萝卜、青菜、猪肉 若干
买了萝卜 1 个,萝卜的未买数目就减 1 ,减到 0 了就是萝卜买齐了
萝卜买齐了,未买种类就减 1 ,未买种类减到 0 了就是所有都买齐了
其他几个变量
left、right 指针,滑动窗口的左右端,初始值都为 0
minLen :保存最小长度,初始值为正无穷,为了让第一次一定被改写
resL : 记录最小子串的起始点
核心思路
主线是 right++ 即扩张窗口,右指针超出 s 串就停止循环
右指针指向的新字符,如果是目标字符,就代表它的缺失个数 - 1
缺失的个数如果因此变 0 ,则缺失的种类就 - 1
扩张窗口,直到当前窗口包含了所有字符,此时 missingType === 0
收缩窗口,并且只要不影响当前窗口包含所有字符,就一直收缩
收缩带来产生最小串的可能,计算长度,与 minLen 比较
左指针指向的字符,如果是目标字符,它会被舍弃,它的缺失个数 + 1
缺失个数如果因此 > 0 ,缺失的种类数 + 1
右指针步进 1 ,收缩窗口
"""
resL = - 1
min_len = len ( s) + 1
map = { }
missing_type = 0
for i in t:
if i in map :
map [ i] += 1
else :
missing_type += 1
map [ i] = 1
left = 0
for right in range ( len ( s) ) :
right_char = s[ right]
if right_char in map :
map [ right_char] -= 1
if map [ right_char] == 0 :
missing_type -= 1
while missing_type == 0 :
if min_len > right - left + 1 :
min_len = right - left + 1
resL = left
left_char = s[ left]
if left_char in map :
map [ left_char] += 1
if map [ left_char] > 0 :
missing_type += 1
left += 1
return '' if resL == - 1 and min_len == ( len ( s) + 1 ) else s[ resL: resL + min_len]