一起刷LeetCode——最小覆盖子串(滑动窗口)

89 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第17天,点击查看活动详情

最小覆盖子串

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

分析

  • 根据题意可知,要得到s覆盖t的最小子串,在遍历s的时候维护一个区间,这个区间里有t中的所有字母,用一个变量维护这个区间的长度,长度最小的那个区间对应的子串就是输出的答案
  • 对于这个变化的区间,能比较容易的想到滑动窗口,这次的滑动窗口是长度可变的窗口,配合双指针指向窗口的首尾
  • 在s中有t的字母时,记录匹配到的字母的个数,当这个个数等于t的长度的时候,此时的窗口是能覆盖t的,当出现这种情况时,可以通过移动滑动窗口的左指针来减小长度,同时需要维持覆盖t的条件,记录符合的子串,当不符合覆盖t的条件时,滑动窗口的右指针继续遍历,如此循环往复直至遍历结束
  • 如何在s中匹配t,可以通过对象或者Map,通过字符-字符出现的次数这样的pair去管理t,在管理t的字母这里有个陷阱,即t有重复字符的情况,所以要处理重复字符的情况,需要记录字符种类的个数,在匹配的时候也是记录匹配到的字符种类的个数

代码

/**
 * @param {string} s
 * @param {string} t
 * @return {string}
 */
var minWindow = function(s, t) {
  let map = {}, uniqueChars = 0;
  for (let char of t) {
    if (char in map) {
      map[char] += 1;
    } else {
      map[char] = 1;
      uniqueChars += 1;
    }
  }
  
  let ans = '';
  let left = 0, match = 0;
  for (let right = 0; right < s.length; right++) {
    let rightChar = s[right];
    if (rightChar in map) {
      map[rightChar] -= 1;
      if (map[rightChar] === 0) match += 1;
    }
    
    if (match === uniqueChars) {
      while (match === uniqueChars) {
        let leftChar = s[left++];
        if (map[leftChar] === 0) match -= 1;
        map[leftChar] += 1;
      }
      let solution = s.slice(left-1, right+1);
      ans = (ans === '')? solution: (ans.length > solution.length)? solution: ans;
    }
  }
  return ans;
}

总结

  • 这道题目扩展了滑动窗口的思路,滑动窗口长度也可以是变化的,在匹配字符串的时候,也能总结出一套思路,使用Map管理被匹配的字符,在遍历的时候通过对Map的value的操作从而确定是否匹配成功
  • 今天也是有收获的一天