76. 最小覆盖子串(难)

题目

  • 给你一个字符串 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 的子串中,
因此没有符合条件的子字符串,返回空字符串。

Python

滑动窗口+备忘录

class Solution:
    def minWindow(self, s: str, t: str) -> str:
        need = {}    # 存储字符串 t 中各个字符的需求量
        window = {}  # 存储滑动窗口中各个字符的出现次数
        for c in t:#遍历字符串t
            need.setdefault(c, 0)#访问不存在的键时自动创建并将值设置为 0
            need[c] += 1  # 统计字符串 t 中各个字符的需求量

        left = 0    # 滑动窗口的左指针
        right = 0   # 滑动窗口的右指针
        valid = 0   # 记录满足需求的字符数
        start = 0   # 最小覆盖子串的起始位置
        length = float('inf')   # 最小覆盖子串的长度

        while right < len(s):
            c = s[right]    # 当前字符
            right += 1      # 右指针右移

            if c in need:#当前字符是目标字符中的
                window.setdefault(c, 0)#访问不存在的键时自动创建并将值设置为 0
                window[c] += 1  # 更新滑动窗口中当前字符的出现次数
                if window[c] == need[c]:
                    valid += 1  # 如果滑动窗口中当前字符的出现次数达到需求量,增加满足需求的字符数

            while valid == len(need):#每个字符的次数都达到了要求
                if right - left < length:#当前窗口的长度是否小于已记录的最小覆盖子串长度 length。
                    start = left#如果是,则更新最小覆盖子串的起始位置和长度
                    length = right - left  # 更新最小覆盖子串的起始位置和长度

                d = s[left]   # 将要移出窗口的字符
                left += 1     # 左指针右移

                if d in need:#当前字符是目标字符中的
                    if window[d] == need[d]:#如果滑动窗口中当前字符等于目标字符的值
                        valid -= 1   # 如果移出窗口的字符导致窗口不再满足需求,则减少满足需求的字符数
                    window[d] -= 1  # 更新滑动窗口中移出字符的出现次数

        return "" if length == float('inf') else s[start:start + length]  # 返回最小覆盖子串

JavaScript

滑动窗口+备忘录

  • 思路:用一个备忘录1记录t串有哪些字母及字母出现的次数;再定义一个备忘录2记录滑动窗口中的字母及出现次数,遍历s串往备忘录2中记录,右指针右移(扩大窗口)一直到满足备忘录1的字母和次数都满足;接着右移左指针(缩小窗口)并减少备忘录2的记录,窗口中的字符不再符合条件,窗口左边界left不再继续移动;循环上面的过程,再移动 right 试图使窗口中的字符再次符合要求,之后移动 left 缩小窗口… 直到 right 指针到达字符串 S 的末端
var minWindow = function(s, t) {
  // 需要的字符计数
  let need = {};
  // 窗口中的字符计数
  let window = {};
  
  // 统计字符串 t 中每个字符的数量
  for (let a of t) {
    need[a] = (need[a] || 0) + 1; // 如果字符已存在则加1,否则初始化为1
  }
  
  // 初始化左右指针
  let left = 0, right = 0;
  
  // 记录满足需求的字符种类数量
  let valid = 0;
  // 最小覆盖子串的起始索引和长度(很大的数)
  let start = 0, len = Number.MAX_VALUE;
  
  // 扩展右指针,遍历字符串 s
  while (right < s.length) {
    // 将即将移入窗口的字符
    let c = s[right];
    // 右移窗口
    right++;
    
    // 如果字符在需要的字符中,更新窗口中的统计
    if (need[c]) {
      window[c] = (window[c] || 0) + 1; // 更新窗口中字符的计数
      // 如果窗口中当前字符的数量满足需要,valid 增加
      if (window[c] == need[c]) {
        valid++;
      }
    }
    
    // 当 valid 等于需要的字符种类数量时,收缩窗口
    while (valid == Object.keys(need).length) {
      // 更新最小覆盖子串的信息
      if (right - left < len) {//如果当前满足条件的窗口大小小于原来的窗口大小才更新
        start = left; // 更新起始索引
        len = right - left; // 更新最小长度
      }
      //缩小窗口
      // 即将移出窗口的字符
      let d = s[left];
      // 左移窗口
      left++;
      
      // 如果移除的字符在需要的字符中,更新窗口的计数
      if (need[d]) {
        // 如果移除后不满足需要,valid 减少
        if (window[d] == need[d]) {
          valid--;
        }
        window[d]--; // 更新窗口中字符的计数
      }
    }
  }
  
  // 返回最小覆盖子串,如果没有找到则返回空字符串
  return len == Number.MAX_VALUE ? "" : s.substr(start, len);
};
posted @ 2024-03-11 20:51  Frommoon  阅读(4)  评论(0编辑  收藏  举报