题目
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:
- 对于
t中重复字符,我们寻找的子字符串中该字符数量必须不少于t中该字符数量。 - 如果
s中存在这样的子串,我们保证它是唯一的答案。 示例 1:
输入: s = "ADOBECODEBANC", t = "ABC"
输出: "BANC"
解释: 最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。
示例 2:
输入: s = "a", t = "a"
输出: "a"
解释: 整个字符串 s 是最小覆盖子串。
示例 3:
输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
思路
理清思路:
- 如果s的子[a...b]区间包含了t的所有字符,那么任何包含[a...b]区间的任意区间都不是最小范围的区间,也不是最小覆盖子串
如何判断S的子串包含了T中的所有字符?
参考做法:分别统计S的子串和T中每个字符出现的次数,然后逐个对比。
解法: 滑动窗口+distance控制滑动
对于判断S的子串包含T的所有子串,可以通过频次比较。
如何计算s和t的频次?
s和t内部都是字符串,那么将字符串对应成ascii码对应的数字0-127,可以设置2个长度为128的数组,分别命名为tFreq、winFreq
tFreq: 统计t的字符出现频次数组winFreq: 统计滑动窗口内字符出现频次的数组
如何判断s和t的字符频次一致?
下一个问题来了,如何判断s和t的频次一致?
引入变量distance,通过判断distance==len(t)的长度来决定窗口是否已包含了t
distance计算规则:
- 如果winFreq[chr] < tFreq[chr]时,说明该窗口chr字符的个数不够,滑动窗口未包含t所有chr字符,还需要right指针继续移动,此时distance+1
- 如果winFreq[chr] >= tFreq[chr]时,说明该窗口的chr字符的个数已经够了,chr字符的distance不需要增加了
如何设置滑动窗口移动or停止规则?
- 移动:滑动窗口不包含t所有字符时,一直右移,可以转化为 distance < len(t)
- 停止:滑动窗口包含了t所有字符时,可能存在最优解,因为此时再往右移动,一定不是最优解。
当滑动窗口停止时,需要来找到包含t的所有字符的最小窗口,滑动窗口的左边界不断收缩,left指针依次右移,每次移一位,判断distance是否等于len(t),直到不相等时,说明是该滑动窗口下的最优解。
伪代码
// winFreq统计字符在滑动窗口的频次
int[] winFreq = new int[128]
int[] tFreq = new int[128]
//更新t的频次
...
//
begin = 0
minLen = len(s) + 1
distance = 0
left = 0
right = 0
// 滑动窗口模板
while right < sLen:
// 判断s[right]在 tFreq 和 winFreq 的频次比较
// 小于: distance++
// winFreq的s[right]++
right += 1
// 满足包含所有t,寻找最优解过程
while distance == tLen:
// 更新包含t的最小区间,以及起始点
if right - left < minLen:
// 这里right已经提前+1了,所以minLen直接用right-left即可
minLen = right - left
begin = left
// 判断winFreq[s[left]]是否等于tFreq[t[left]],如果等于的话,说明这个移动后区间不满足包含t的所有字符,distance-=1
if winFreq[s[left]] == tFreq[t[left]]:
distance -= 1
winFreq[s[left]]--
left--
//判断minLen是否等于初始值,此时说明没有包含t的区间
if minLen == len(s) + 1:
return ""
// 返回最终结果
return s[begin: begin + minLen]
代码一
class Solution:
def minWindow(self, s: str, t: str) -> str:
sLen = len(s)
tLen = len(t)
if sLen == 0 or tLen == 0 or sLen < tLen:
return ""
# 初始化tFreq和winFreq
tFreq = [0 for _ in range(128)]
winFreq = [0 for _ in range(128)]
for c in t:
tFreq[ord(c)] += 1
# 设置窗口指针变量、滑动条件变量、结果变量
left = 0
right = 0
begin = 0
minLen = sLen + 1
distance = 0
while right < sLen:
if tFreq[ord(s[right])] > winFreq[ord(s[right])]:
distance += 1
winFreq[ord(s[right])] += 1
right += 1
while distance == tLen:
if right - left < minLen:
minLen = right - left
begin = left
if tFreq[ord(s[left])] == winFreq[ord(s[left])]:
distance -= 1
winFreq[ord(s[left])] -= 1
left += 1
if minLen == sLen + 1:
return ""
return s[begin:begin+minLen]
另外一种distance计算方式
distance初始设为tLen,然后通过控制tLen==0来控制窗口的滑动。
代码二
class Solution:
def minWindow(self, s: str, t: str) -> str:
sLen = len(s)
tLen = len(t)
if sLen == 0 or tLen == 0 or sLen < tLen:
return ""
tFreq = [0 for _ in range(128)]
winFreq = [0 for _ in range(128)]
for c in t:
tFreq[ord(c)] += 1
left = 0
right = 0
begin = 0
minLen = sLen + 1
distance = tLen # 改动
while right < sLen:
if tFreq[ord(s[right])] > winFreq[ord(s[right])]:
distance -= 1 # 改动
winFreq[ord(s[right])] += 1
right += 1
while distance == 0:
if right - left < minLen:
minLen = right - left
begin = left
if tFreq[ord(s[left])] == winFreq[ord(s[left])]:
distance += 1 # 改动
winFreq[ord(s[left])] -= 1
left += 1
if minLen == sLen + 1:
return ""
return s[begin:begin+minLen]