LeetCode 76-最小覆盖子串

63 阅读4分钟

题目

给你一个字符串 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中每个字符出现的次数,然后逐个对比。 image.png

解法: 滑动窗口+distance控制滑动

对于判断S的子串包含T的所有子串,可以通过频次比较。

如何计算s和t的频次?

s和t内部都是字符串,那么将字符串对应成ascii码对应的数字0-127,可以设置2个长度为128的数组,分别命名为tFreqwinFreq

  • 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]