leecode-LCR 017. 最小覆盖子串(golang版本)

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 := 0
	res := s
	has := false
	for 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 = true
				if 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就可以统计滑动窗口是否完全包含某一个字符串。

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值