76. 最小覆盖子串 - 力扣(LeetCode)

题目:

        给你一个字符串 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 为长字符串,字符串 t 为短字符串,要在 s 中查找包含 t 所有字符的最小字符串片段,对字符的顺序无要求。

        因此首先,在字符串 s 中建立快慢指针放在队首,这里初始窗口设计为 window = (0, len(s) + 1)内部没有元素存在, 后续遍历会扩大形成滑动窗口,窗口内包含 t 中所有的字符串。之后,通过指针 i 遍历 t ,建立字典 Cnt_t 来储存字符串 t 中每个字符 出现的次数。need_Cnt_t 来记数 Cnt_t 中有几种字符。

        以 示例1 为例。Cnt_t = {A:1, B:1, C:1}need_Cnt_t = 3,当快慢指针在 s 中滑动时,快指针 right 向右移动,包含第一个字符 A 时,得到 Cnt_t = {A:0, B:1, C:1}A 字符完成配对,此时 need_Cnt_t - 1 = 2。继续向右,直到第一次 Cnt_t 完全配对,得到子串为 ADOBEC ,此时 need_Cnt_t = 0 。继续向右,当子串为 ADOBECODEB 时,因为有 2个B ,不符合 Cnt_t,所以 left 指针向右移动,跳过 第一个 B 和其他 无关字符 ,得到子串为 ECODEB。此时 A 的数量不符合 Cnt_tright 指针继续向右。依次类推,直到获得最小覆盖子串 BANC

题解如下:

class Solution:
    def minWindow(self, s: str, t: str) -> str:
        left, right = 0, 0
        # 创建一个字典来记录字符串t中每个字符出现的次数
        Cnt_t = {}
        # 初始化最小窗口的起始和结束索引,长度设为比s长1,方便后面判断
        window = (0, len(s) + 1)

        # 统计字符串t中每个字符的出现次数
        for i in t:
            if i not in Cnt_t:
                Cnt_t[i] = 1
            else:
                Cnt_t[i] += 1
        
        # 字符串t需要匹配的字符种类数
        need_Cnt_t = len(Cnt_t)
        
        # 遍历字符串s,left指针从左到右移动
        while left < len(s):
            # 移动right指针扩展窗口,直到窗口包含所有t中的字符
            while right < len(s) and Cnt_t != 0:
                # 如果当前字符在Cnt_t字典中
                if s[right] in Cnt_t:
                    # 减少该字符的计数
                    Cnt_t[s[right]] -= 1
                    # 如果该字符的计数刚好为0,说明匹配到了所有该字符
                    if Cnt_t[s[right]] == 0:
                         need_Cnt_t -= 1
                # 如果当前字符不在Cnt_t字典中,right指针向右移动一位跳过该字符
                right += 1
            
            # 移动left指针,跳过不在Cnt_t字典中的字符
            while left < len(s) and s[left] not in Cnt_t:
                left += 1
            
            # 如果当前窗口包含t中所有字符且数量一致(need_Cnt_t == 0)
            # 并且窗口长度 小于 记录的最小窗口,更新最小窗口
            if right - left < window[1] - window[0] and need_Cnt_t == 0:
                window = (left, right)
            
            # 如果left指针还在字符串范围内
            if left < len(s):
                # 如果当前字符在Cnt_t字典中,增加其计数
                Cnt_t[s[left]] += 1
                # 如果该字符的计数大于0,说明窗口中不再包含所有该字符
                if Cnt_t[s[left]] == 1:
                    need_Cnt_t += 1
                # left指针向右移动一位
                left += 1
        
        # 如果最小窗口的结束索引大于字符串s的长度,说明没有找到有效窗口,返回空字符串
        if window[1] > len(s):
            return ""
        # 否则返回最小窗口对应的子字符串
        return s[window[0]:window[1]]

代码解释:

  1. 初始化部分

    • leftright指针初始化为0,表示窗口的起始位置。

    • Cnt_t字典用于记录字符串t中每个字符的出现次数。

    • window元组初始化为(0, len(s) + 1),表示一个无效的窗口(长度比s长1)。

  2. 统计t中字符的出现次数

    • 遍历字符串t,将每个字符及其出现次数存入Cnt_t字典。

  3. need_Cnt_t变量

    • 表示需要匹配的字符种类数,初始化为Cnt_t字典的长度。

  4. 滑动窗口逻辑

    • 外层while循环以left指针为主,控制窗口的左边界移动。

    • 内层while循环以right指针为主,扩展窗口直到包含所有t中的字符。

      • 如果当前字符在Cnt_t中,减少其计数。如果计数减到 0,说明该字符已完全匹配,need_Cnt_t1

      • right指针向右移动。

    • 移动left指针,跳过不在Cnt_t中的字符。

    • 当窗口包含所有t中的字符(need_Cnt_t == 0)且长度小于当前最小窗口时,更新window

    • 如果left指针仍在范围内,恢复当前字符的计数(如果在Cnt_t中),并根据计数调整need_Cnt_t,然后left指针向右移动。

  5. 结果处理

    • 如果最小窗口的结束索引大于s的长度,说明没有找到有效窗口,返回空字符串。

    • 否则返回最小窗口对应的子字符串。

你可能感兴趣的:(#,数组,leetcode,linux,算法,数据结构,数组,python)