算法笔记4:最小覆盖子串

152 阅读2分钟

76.最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""

可知这个子串肯定是连续的几个在 s 中的字符组成的字符串,而且对于某个字符是否应该添加到结果里或者删除的条件都可以明确定义,所以可以采用滑动窗口。

代码如下:

const minWindow = (s, t) => {
  // count how many types of chars there are in `t`
  // and how many times they appear.
  // this is to check if current char combination meets the criteria
  const tMap = t.split("").reduce((prev, char) => {
    if (prev[char]) {
      prev[char]++;
    } else {
      prev[char] = 1;
    }
    return prev;
  }, {});
  
  // a counter to check if current state is valid.
  // if this number equals to the amount of types of chars in `t`,
  // then this string is valid.
  let valid = 0;
  // create a cascaded map to store the state along the calculation
  const currMap = Object.keys(tMap).reduce((prev, char) => ({
    ...prev,
    [char]: 0,
  }), {})
    
  let l = 0, r = 0;
  let start = 0, len = Number.MAX_SAFE_INTEGER;
  while(r < s.length) {
    // the char to the right, which is the one about to be added
    const charR = s[r];
    r++;

    // if this new char is required in `t`
    if (tMap[charR]) {
      currMap[charR]++;
      // if the new count of this char equals the map,
      // then a new type of char is full
      if (currMap[charR] === tMap[charR]) {
        valid++;
      }
    }

    // if the current substring is valid,
    // then the window needs to be shrank
    while(valid === Object.keys(tMap).length) {
      // first, we update the result if needed
      if (r - l < len) {
        len = r - l;
        start = l;
      }

      // the char to the left, which is the one about to be removed
      const charL = s[l];
      l++;

      // same as above, if this char is required in `t`
      if (tMap[charL]) {
        if (currMap[charL] === tMap[charL]) {
          // should make it invalid,
          // because by shrinking the window this substring is no longer valid
          valid--;
        }
        currMap[charL]--;
      }
    }
  }

  return len === Number.MAX_SAFE_INTEGER ? '' : s.substr(start, len);
}

总结:

  • 滑动窗口就是先扩张,满足条件后收缩再找最优解。
  • 先分析一下问题,看下是否可以明确地判断当前窗口内的值是否符合要求。如果不行就不适用滑动窗口方法。
  • 注意收缩过程中的每一步中,状态更新的正确性。