leecode最小覆盖字串
leecode链接地址
给定两个字符串 s 和 t 。返回 s 中包含 t 的所有字符的最短子字符串。如果 s 中不存在符合条件的子字符串,则返回空字符串 “” 。
如果 s 中存在多个符合条件的子字符串,返回任意一个。
题解
func minWindow(s string, t string) string {
m := make(map[uint8]int)for i := 0; i < len(t); i++ {m[t[i]]++}valid := len(m)left := 0res := shas := falsefor right := 0; right < len(s); right++ {if v, ok := m[s[right]]; ok {m[s[right]]--if v == 1 {valid--}}if valid == 0 {for right-left >= len(t)-1 {has = trueif v, ok := m[s[left]]; ok {if v == 0 {if right-left < len(res) {res = s[left : right+1]}break}m[s[left]]++}left++}}}if !has {return ""}return res
}
核心点一: 滑动窗口
我们分析一下题目,需要判断s 中包含 t 的所有字符的最短子字符串,说明需要在字符串s中寻找一个连续的区间,这个区间包含的字符需要囊括的字符串t中的全部字符。一般这种寻找连续区间的题目,我们第一反应就应该是使用双指针的滑动窗口来处理
我们现在以s=ADOBECODEBANC,t=ABC为例,逐步分析滑动窗口的过程
区间范围 | 是否包含字符串t |
---|---|
[0,0) | 否 |
[0,1) | 否 |
[0,2) | 否 |
[0,4) | 否 |
[0,5) | 否 |
[0,6) | 是 |
当前的滑动窗口包含的字符串是ADOBEC,这时已经完全包含字符串t的字符了。此时的滑动窗口指针分别是0,6,题目要求最短子字符串,这就意味着我们的工作还不能结束。相比于字符串t而言,此刻的滑动窗口内的字符数量远远大于t,这说明滑动窗口可能还有继续优化的空间。当右边的指针移动到完全包含字符串t时,左边的指针还有机会可以收缩窗口的空间。左边指针对应的字符是A,此刻滑动窗口内的字符A的数量为1,假如移动滑动窗口左边界,会使得窗口内不能完全包含t的字符,所以还是只能移动滑动窗口右边界。只有当滑动窗口内多加入一个A字符时,左侧边界就可以放弃A字符,却又不会影响整个滑动窗口能够包含t中的元素。
区间范围 | 是否包含字符串t | 是否可以移动左边界 |
---|---|---|
[0,7) | 是 | 否 |
[0,8) | 是 | 否 |
[0,9) | 是 | 否 |
[0,10) | 是 | 否 |
[0,11) | 是 | 是 |
对于当前[0,11)包含的字符串,似乎不是最优的滑动窗口,因为左侧的A可以舍弃却不会影响最终的包含关系。 |
区间范围 | 是否包含字符串t | 是否可以移动左边界 |
---|---|---|
[1,11) | 是 | 是 |
[2,11) | 是 | 是 |
[3,11) | 是 | 否 |
[4,11) | 是 | 否 |
[5,11) | 是 | 否 |
我们的滑动窗口还没有探索完毕整个字符串,我们再次移动右边界,探索滑动窗口的可能性。 | ||
区间范围 | 是否包含字符串t | 是否可以移动左边界 |
– | – | – |
[5,12) | 是 | 否 |
[5,13) | 是 | 是 |
[6,13) | 是 | 是 |
[7,13) | 是 | 是 |
[8,13) | 是 | 是 |
[9,13) | 是 | 否 |
此时,我们可以把上面所有包含t的时间窗口的字符串进行统计,找出最小的字符串,就是题解。 |
核心点二: 比较两个字串全排列是否相等
在这个题目中, 需要判断s 中包含 t 的所有字符的最短子字符串,可以首先思考一点,怎么比较两个字符串是相等的,也就是上面一个步骤中比较滑动窗口是否包含t。这里的相等不是一摸一样,而是包含相同的字符,如下所示的两个字符串就是一个全排列相等的字符串,因为他们包含了相同的字符:
字符串abcd: a 1个 b 1个 c 1个 d 1个
字符串dcba: a 1个 b 1个 c 1个 d 1个
我们来思考一下应该怎么写一个函数来判断这两个字符是否相等。我的第一想法是构建一个两个map结构的计数器,分别统计两个字符串的字符数量,计数器以字母为key值,以字母在字符串中的数量为value值,然后比较两个map是否一致。如果写出来大概是这么一段代码。
func equalString(a, b string) bool {ma := counnter(a)mb := counnter(b)return equalMap(ma,mb)}func counnter(s string)map[uint8]int{ma := make(map[uint8]int)for i := 0; i < len(s); i++ {ma[s[i]]++}return ma
}func equalMap(a,b map[uint8]int) bool{if a==nil||b==nil||len(a)!= len(b){return false}for k, v := range a {vv,ok:=b[k]if ok&&vv==v{continue}else {return false}}return true
}
有没有更简单的方案呢,很显然我们可以转换一下思路,我们来重新思考一下mb := counnter(b)。这个map也可以表示为:滑动窗口内还需要几个某种字符就可以完全涵盖字符串t中的字符了。
举个例子,
对于这么一个map,当前滑动窗口是[0,0),即窗口中没有任何字符:
a:1
b:2
c:3
我们可以这么理解,我们还需要1个a,2个b,3个c,才能完全涵盖字符串t
如果滑动窗口发生了变化[0,1),这时候我们在滑动窗口中新增了一个字符a,那么此时我们还需要个b,3个c,才能完全涵盖字符串t,但是这时我们可以发现,还需要2类有效字符的加入,对于滑动窗口而言是有效的。
假设map变成了a:-2
b:-3
c:1
此时,很显然只需要一类有效字符了,这时候我们在滑动窗口中新增了一个字符c,那么我们可以刻得到
a:-2
b:-3
c:0,很显然这个滑动窗口已经满足需求了。
综上所述,使用一个有效字段数量,和一个map就可以统计滑动窗口是否完全包含某一个字符串。