Skip to content

Latest commit

 

History

History
186 lines (144 loc) · 4.6 KB

76-最小覆盖子串.md

File metadata and controls

186 lines (144 loc) · 4.6 KB

76-最小覆盖子串

原题 s 字符串中含有 t 字符串所有字母的子串(可乱序),最短是哪个 题解参考

给定两个字符串 s 和 t,长度分别是 m 和 n,返回 s 中的 最短窗口 子串,使得该子串包含 t 中的每一个字符(包括重复字符)。如果没有这样的子串,返回空字符串* *""。

测试用例保证答案唯一。

 

示例 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) 时间内解决此问题的算法吗?

/*
当前函数是一张`map` 同时表示「还缺几个」和「多出来几个」,所以`map[c]` 可能变成负数,理解成本高。更直观的版本是:

`need` :`t` 里每个字符需要几个
`window` :当前窗口里每个字符已有几个
`valid` :已有数量满足需求的字符种类数

*/
const minWindow = function (s, t) {
  if (s.length === 0 || t.length === 0) return '';

  const need = {};
  const window = {};

  for (const c of t) {
    need[c] = (need[c] || 0) + 1;
  }

  const needKinds = Object.keys(need).length;
  let valid = 0;
  let left = 0;
  let start = -1;
  let minLen = s.length + 1;

  for (let right = 0; right < s.length; right++) {
    const c = s[right];

    if (need[c] !== undefined) {
      window[c] = (window[c] || 0) + 1;

      if (window[c] === need[c]) {
        valid++;
      }
    }

    while (valid === needKinds) {
      // 判断边界条件
      const len = right - left + 1;
      if (len < minLen) {
        minLen = len;
        start = left;
      }

      // 窗口左移
      const d = s[left];
      if (need[d] !== undefined) {
        if (window[d] === need[d]) {
          valid--;
        }
        window[d]--;
      }

      left++;
    }
  }

  return start === -1 ? '' : s.substring(start, start + minLen);
};

const minWindow = function (s, t) {
  let minLen = s.length + 1;
  let start = s.length;
  let map = {};
  let missingType = 0;
  for (const c of t) {
    if (!map[c]) {
      missingType++;
      map[c] = 1;
    } else {
      map[c]++;
    }
  }

  let l = 0,
    r = 0;
  for (; r < s.length; r++) {
    let rightChar = s[r];
    if (map[rightChar] !== undefined) {
      map[rightChar]--;
    }
    // 也就是遍历到这一步的时候,所有的数字都找到了,这时候右边界已经确定了
    // 然后开始通过下面的 while 来找到左边界
    if (map[rightChar] === 0) {
      missingType--;
    }

    while (missingType === 0) {
      if (r - l + 1 < minLen) {
        minLen = r - l + 1;
        start = l;
      }

      // 还是没有明白这两个 if 条件语句是要用来干嘛
      // 应该是用来判断退出 while 的边界条件
      // 也就是不断将左边界右移,一旦发现右移的时候,遇到了需要匹配的字符串,就中断

      // 也不是说遇到了匹配的字符串就中断,因为如果全部拿到了匹配的字符串后,部分字符串可能匹配的次数多余需要匹配的次数
      // 所以在上面有 map[rightChar]--,可能到最终 map[rightChar] 是个负数
      // 所以在这里我们需要保证次数,通过map[leftChar]++,如果map[leftChar]大于0了就退出

      /* 
        是因为上面是当 missingType === 0 进入时候的计算,然后需要继续往后走,所以这里主要的是 l++,然后 l++ 之后,会带来什么情况
        也就是在当前字符串中,如果去掉了 s[l] 会有什么情况,这两个 if 就是在计算这种情况
      */
      let leftChar = s[l];
      if (map[leftChar] !== undefined) {
        map[leftChar]++;
      }
      if (map[leftChar] > 0) {
        missingType++;
      }

      l++;
    }
  }

  if (start === s.length) {
    return '';
  }

  return s.substring(start, start + minLen);
};

let s = 'ADOBECODEBANC',
  t = 'ABC';
let result = minWindow(s, t);
console.log(result);