不容错过的“最小覆盖子串”解法

题目来源Leetcode最小覆盖子串

一.题目

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。

示例:

输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"

说明:

  • 如果 S 中不存这样的子串,则返回空字符串 “”。
  • 如果 S 中存在这样的子串,我们保证它是唯一的答案。

二.解题思路

对于这道题思路而言其实思路应该很简单,可能你的第一想法是直接暴力遍历字符串S的所有子串,然后看其中是否包含了T串中的所有字符串,然后找出满足包含关系的最小子串,但是实际上这样的算法时间复杂度太高了,而且这样查找会做很多无用功,因此我果断放弃了直接暴力搜索的念头。
No
虽然不能直接采用暴力法来求解,但是解题思路已经很接近了,我们需要在S串中找到包含T串所有字符的子串,然后找到其中的最小子串即可。这个时候滑动窗口就可以闪亮登场了,可能你会问我为啥会想到滑动窗口,我只能说直觉,当然这直觉需要在大量练习的基础上。

2.1.如何使用滑动窗口

首先,需要构造:

  • 一个空字符串char,它用来保存在遍历S串时,那些包含在T串中的字符;
  • 一个索引列表idx,它保存了对应char中的字符在S串中的索引,索引列表idx的第一个值最后一个值构成S串中当前串的左右边界

滑动窗口滑动过程的伪代码如下:

res = infinity #结果为无穷大
l,r = 0,0#S中包含T中所有字符最小子串的左右边界
for ch in S:
	#右指针右移
	if T 包含字符ch:
		将ch添加到char中
		将ch对应的索引添加到idx中
	#左指针右移,以寻找当前子串中包含T串所有字符的最小子串
	while char != "" AND char中包含T中的所有字符:
		取出idx[0] 和idx[-1] #取出idx中第一个和最后一个值
		if 根据两个索引计算出的串长 < res:
			l,r = idx[0],idx[-1]
			res = r - l + 1
		char串中移出第一个字符
		idx索引列表移出对应的索引值		

2.2.如何验证S串的某个子串是否包含了T串中的所有字符

现在问题就变成了如何验证S串的某个子串是否包含了T串中的所有字符,该包含关系需要满足两个条件

  • S串的子串包含T串中所有字符的种类;
  • S串中对应T串字符的个数和T串要一致;

要达成这样的目的,需要统计出T串中的每个字符出现的次数,这种情况用字典比较合适。
例如T串为 “ABCC”,则对应字典ditt为:

ditt = {
	'A':1,
	'B':1,
	'C':2
}

同时初始化相同结构的一个字典dits,用于在遍历S串时记录当前子串中包含T串各字符的情况

dits = {
	'A':0,
	'B':0,
	'C':0
}

判断是否包含的算法为

for key in ditt.keys():
	if dits[key] < ditt[key]:#S串中的当前子串包含T串的某个字符数小于对应字符在T串中的数目
		return False
return True

三.算法的图解演示

这里以S = "ADOBECODEBANC", T = "ABC"作为示例进行演示,根据T串构造出来的字典ditt和dits分别为:

ditt = {
	'A':1,
	'B':1,
	'C':1
}
dits = {
	'A':0,
	'B':0,
	'C':0
}

步骤1,遍历S找到’A’包含在T串中,此时将’A’及其对应索引0分别加入到char,idx中,同时dits[‘A’]的值加1
步骤1
步骤2,继续遍历发现’B’包含在T串中,此时将’B’及其对应索引3分别加入到char,idx中,同时dits[‘B’]的值加1
步骤2
步骤3,继续遍历发现’C’包含在T串中,此时将’C’及其对应索引5分别加入到char,idx中,同时dits[‘C’]的值加1。此时CHAR中的包含T串中各个字符数都不小于T串中对应字符的数目,找到一个包含T串所有字符的串,串长为(5 - 0 + 1) = 6
步骤3
步骤4,滑动窗口左指针右移一格,即将char中的’A’及idx中对应的索引移出,同时将dits[‘A’]的值减1
步骤4
步骤5,S串继续向前遍历发现’B’包含在T中,此时将’B’及其对应索引9分别加入到char,idx中,同时dits[‘B’]的值加1
步骤5
步骤6,S串继续向前遍历发现’A’包含在T中,此时将’A’及其对应索引10分别加入到char,idx中,同时dits[‘A’]的值加1,此时找到包含T串所有字符的子串,串长为(10 - 3 + 1) = 8
步骤6
步骤7,滑动窗口左指针右移一格,即将CHAR中第一个’B’及其对应的索引分别从char,idx移出,同时dits[‘B’]减1。此时滑动窗口中包含的字符仍包含T串中的所有字符,该子串的串长为(10 - 5 + 1) = 6。
步骤7

以此类推,最终即可找到最终包含T串中所有字符的最小子串"BANC",其串长为4.

四.示例代码

class Solution:
    def minWindow(self, s: str, t: str) -> str:
        if len(s) < len(t):#s串长小于t直接返回空串
            return ""
		#统计T串中个字符的出现频数
		#并定义一个相同的结果保存当前串包含T串各字符的频数
        dits,ditt = {},{}
        for ch in set(t):
            if ch not in s:#T串中存在S串中不存在的字符,直接返回空串
                return ""
            dits[ch] = 0
            ditt[ch] = t.count(ch)

        res = float("inf")
        l,r = 0,0
        idx,char = [],"" #定义索引列表,和char字符串
        for i,ch in enumerate(s):
        	#右指针右移
            if ch in t:
                char += ch
                dits[ch] += 1
                idx.append(i)
            #左指针右移
            while char != "" and self.isContain(dits,ditt):
                if res > idx[-1] - idx[0] + 1:
                    l,r = idx[0],idx[-1]
                    res = r - l + 1
                idx.pop(0)
                dits[char[0]] -= 1
                char = char[1:]

        if res == float("inf"):#不存在包含T串所有字符的子串
            return ""
        return s[l:r + 1]
    
    def isContain(self,dits,ditt):
        for key in ditt.keys():
            if dits[key] < ditt[key]:
                return False
        return True

码文不易,请大家多多支持!!!
点赞

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 技术工厂 设计师: CSDN官方博客
应支付0元
点击重新获取
扫码支付

支付成功即可阅读