给你一个字符串 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) 时间内解决此问题的算法吗?
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-window-substring
python
class Solution:
def minWindow(self, s: str, t: str) -> str:
"""
滑动窗口,时间:右指针遍历一次s串,左指针最多遍历一次完整串,即O(2n)->O(n),空间:有限变量+s与t共有的元素O(k),常量级即O(1)
思路:
1.need哈希表记录t中元素及出现次数,needCnt表示t中元素长度或个数,通过这两个变量可以知道加入的元素,没有加入的元素,就不需要每次窗口遍历need获取哪些元素还需加进窗口
在遍历s的过程中,遍历的元素,每次遍历到的value--,即s-t的元素value都是负数,0表示刚好没有,而 s&t的元素,小于0多余,等于0刚好,大于0需要移动右指针扩大窗口
2.移动右指针,扩大窗口,加入新元素
3.移动左指针,缩小窗口,寻找当前窗口的最小
4.当左指针已经是当前右界的最小窗口的左界时,右移左指针,寻找下一个符合条件的窗口
算法:
1.初始化准备工作:
-初始化need哈希表,value=0,遍历t,记录字符及个数,
-needCnt初始化为当前t的长度,
-初始化左指针i在0处,
-初始化返回结果res,右上下界组成,初始化为0到无穷大
2.右指针j右移
-优先判断是否遇到t中元素,是则needCnt--
-need对应字符的value--
-检查当前窗口是否覆盖t中元素,进入3步骤
3.needCnt==0表示当前窗口覆盖t中元素,可以通过左指针右移缩小窗口以获得局部最小覆盖窗口
-检查当前左指针字符,如果need中字符value为0,即当前为局部最小最优解,跳出循环
-不等于0,即小于0,窗口可以优化,need中value++,左指针右移,重复上面及此步骤
-已经是局部最优解,检查与res的大小,取较小值记录,每次记录都应该在左窗口右移缩小时记录
-寻找下一个符合条件的窗口,然后优化窗口寻找局部最优
-当前元素的need value++,needCnt++,左指针i++右移
重复以上步骤
4.如果没有找到res即res还是初始化的上下界,返'',否则返回最小覆盖子串
"""
from collections import defaultdict
need=defaultdict(int) # 初始化哈希表,用于存储遍历的窗口元素及出现次数
for c in t: # 遍历字符t,遇到字符即+1,遍历完即为t中所有元素出现次数的统计
need[c]+=1
needCnt=len(t) # needCnt用于统计遍历窗口中是否全部包含了t的元素
i=0 # 初始化窗口左边界指针,窗口右减左加
res=(0,float('inf')) # 初始化返回结果,0 到 无穷大
for j,c in enumerate(s): # 枚举或遍历字符串s, j为下标,c为字符
if need[c]>0: # 检查字符c次数是否大于0,即遇到t中元素,needCnt--,True表示窗口可以继续右移,j++
needCnt-=1 # 由于只有在t中的元素出现在s中,其value才为正,needCnt--表示此次出现一次,其他非t中字符出现在s中,不进此逻辑判断
need[c]-=1 # 不管遇到什么字符,遍历到就--,非t中元素出现在s中,肯定是多余,也一定是负数
if needCnt==0: #步骤一:滑动窗口包含了所有T元素,>>> 由于neddCnt表示t中字符长度或个数,如果为0,表示当前窗口下,已经包含有所有t中字符,否则j++
while True: #步骤二:增加i,排除多余元素 >>> 通过不断地右移左指针,缩小当前窗口长度,排除非t中字符
c=s[i] # 当前在s中的窗口左指针字符
if need[c]==0: # 检查左指针字符对应的need哈希value是否等于0,不等于0,肯定小于0,多余字符,可右移缩小窗口,假设移动了该字符,由于是左指针,相当于减小窗口,need[c]++,如果大于0,说明窗口还需要扩大,但右指针当前固定为j,所以当前窗口可以作为临时合要求的结果
break # 和符合要求的窗口,应该退出左指针右移
need[c]+=1 # 如果不等于0,说明左指针指向的字符是多余字符,对应字符value++
i+=1 # 上面移动左指针因为是多余字符,可以继续右移左指针, i++
if j-i<res[1]-res[0]: #记录结果 >>> 只要小都应该记录,而且更新,相当于更新窗口长度,同时也是固定了位置
res=(i,j)
need[s[i]]+=1 #步骤三:i增加一个位置,寻找新的满足条件滑动窗口 >>> 当前窗口是包含t中元素的最小窗口,后面左指针元素的need的value++,表示左指针字符次数++,表示需要加入的t中元素
needCnt+=1 # 肯定有某个t中的元素需要在新窗口中加入
i+=1 # 右移左指针,寻找新的符合条件的窗口
return '' if res[1]>len(s) else s[res[0]:res[1]+1] #如果res始终没被更新过,代表无满足条件的结果 >>> 如果res没有更新,res[1] = inf,无穷大,就是没有找到合适的窗口,否则返回最小的子串
if __name__ == "__main__":
s = 'usyiskbleasydknitehw'
t = 'sky'
test = Solution()
print(test.minWindow(s,t))
golang
package main
import "fmt"
func main() {
s := "ADOBECODEBANC"
t := "ABC"
fmt.Println(minWindow(s,t))
}
func minWindow(s string, t string) string {
ori, cnt := map[byte]int{}, map[byte]int{}
for i := 0; i < len(t); i++ {
ori[t[i]]++
}
sLen := len(s)
len := math.MaxInt32
ansL, ansR := -1, -1
check := func() bool {
for k, v := range ori {
if cnt[k] < v {
return false
}
}
return true
}
for l, r := 0, 0; r < sLen; r++ {
if r < sLen && ori[s[r]] > 0 {
cnt[s[r]]++
}
for check() && l <= r {
if (r - l + 1 < len) {
len = r - l + 1
ansL, ansR = l, l + len
}
if _, ok := ori[s[l]]; ok {
cnt[s[l]] -= 1
}
l++
}
}
if ansL == -1 {
return ""
}
return s[ansL:ansR]