Skip to content

最小覆盖字串

力扣第 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 的子串中, 因此没有符合条件的子字符串,返回空字符串。

提示:

  • m == s.length
  • n == t.length
  • 1 <= m, n <= 105
  • s 和 t 由英文字母组成

进阶:你能设计一个在 o(m+n) 时间内解决此问题的算法吗?

题目分析

给定字符串 s,t,从 s 中找到一个字串,使得它包含 t 中所有的字符,并且这个字串要是最小的。

解题思路

  1. 遍历 t,统计每个字符串出现的次数,存到 needMap 中
  2. 给字符串 s 设计左右指针 left 和 right,从第 0 个开始循环
  3. 当右指针小于字符串 s 的长度时,继续往右移动
  4. 设计一个变量 count,记录 t 中的字符出现的次数,设计一个windowMap,记录窗口中的字符出现的次数,当本次字符 windowMap[c] 和 needMap[c] 个数相等,说明此字符出现的次数符合要求,count++
  5. 如果 count 等于 needMap 的 size,说明本次窗口符合要求,记录本次的窗口的起始位置,然后左指针往右移动
  6. 如果移动前左边字符存在于 needMap,那么 count--,本次字符不符合要求
  7. 遍历结束,如果右指针等于 infinity,说明没有找到符合条件的字串,返回空字符串,否则返回 s 中的子串

代码实现

js
/**
 * @param {string} s
 * @param {string} t
 * @return {string}
 */
var minWindow = function (s, t) {
  /**
   * 前提:字串要求是和原字符串一样的顺序 并且 t 字符出现的顺序不一定与字串相等,字符数量一致即可
   * 1.设置一个左右指针,作为滑动窗口边界
   * 2.t 中出现的字符串计数 滑动窗口内出现额字符串数字也计数,
   * 并设置一个 charCount,每次遇到相符的字符串+1
   * 3.遍历边界 右指针滑到最右端停止
   * 4. 当符合字串的要求与上一次对比,如果比上次短,则替换开始和结束节点
   *
   */
  const sLen = s.length;
  const tLen = t.length;
  if (tLen > sLen) return "";
  // 左右指针
  let left = 0;
  let right = 0;
  // 记录 window 中出现字符串次数
  const windowMap = new Map();
  const needMap = new Map();
  // 符合字符串计数
  let charCount = 0;
  for (let i = 0; i < tLen; i++) {
    let preCount = needMap.get(t[i]) || 0;
    needMap.set(t[i], preCount + 1);
  }
  let preLeft = left;
  let preRight = Infinity;
  // 循环停止条件 右指针探到数组最右边
  while (right < sLen) {
    // 右边指针靠右走
    // 当 charCount 和 needMap 的 size 相等,说明滑动窗口找到符合的字串
    // if (charCount === needMap.size) {
    const c = s[right];
    const count = windowMap.get(c) || 0;
    windowMap.set(c, count + 1);
    if (windowMap.get(c) === needMap.get(c)) {
      charCount++;
    }
    while (charCount === needMap.size) {
      // 比较本次窗口和上次窗口大小
      if (preRight - preLeft > right - left) {
        preLeft = left;
        preRight = right;
      }
      const ls = s[left];
      //left++ 缩小窗口
      left++;
      // 如果存在目标值
      if (needMap.has(ls)) {
        // 当数量是相同的时候才需要减1
        if (windowMap.get(ls) === needMap.get(ls)) {
          charCount--;
        }
        // 滑动串口出现计数操作
        windowMap.set(ls, windowMap.get(ls) - 1);
      }
    }
    right++;
  }
  return preRight === Infinity ? "" : s.slice(preLeft, preRight + 1);
};