每日一题- day 9

83 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情

1. 今日语录

情人可以没有,力扣不能断刷。

2. 题目

最小覆盖子串

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

注意:

对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。 如果 s 中存在这样的子串,我们保证它是唯一的答案。

3. 思路

  1. 这虽然是一道hard,但其实思路是相对容易的,难点是细节上的处理。
  2. 滑动窗口类型的题目,其实都很相似。
  3. 一般都是 左指针l,右指针r 开始都为 0,然后 r 先移动,满足题目条件后,记录下当前结果,然后就是 l 开始移动,缩小窗口大小。
  4. 难点一般在于 l 的移动后 该有什么措施?
  5. 以本题为例,左侧l移动后,遇到了 t表中 出现的字符,这时窗口本身会立即多一个可用字符,这就推动了 r 继续向右移动。
  6. 易错点:一开始没考虑到 t 里的重复元素, 单纯用includes方法判断。

4. 代码

var s = "ADOBECODEBANC";
var t = "ABC";

var minWindow = function (s, t) {
    // 根据数组t新建个map表,记录每个字符的出现个数
    let map = new Map()
    for (let ch of t) {
        if (map.has(ch)) {
            map.set(ch, map.get(ch) + 1)
        } else {
            map.set(ch, 1)
        }
    }
    // 滑动窗口 [l,r]
    let l = r = 0;
    // 字符串的种类个数
    let size = map.size;
    let res = '';
    while (r < s.length) {
        if (map.has(s[r])) {
            let n = map.get(s[r])
            map.set(s[r], n - 1)
            // 某个字符匹配完,剩余匹配数size -= 1
            if (n === 1) {
                size--;
            }
        }
        // size == 0时,说明所有字符都匹配完
        while (size === 0) {
            let str = s.slice(l, r + 1)
            // res 为 出现过的str 的 最小值
            if (res) {
                res = res.length <= str.length ? res : str;
            } else {
                res = str;
            }
            // 左侧开始滑动,一方面是试探性缩减窗口的大小
            // 另一面是恢复size的可用个数,使窗口能推动下去
            if (map.has(s[l])) {
                const n = map.get(s[l])
                map.set(s[l], n + 1)
                // 某一字符从0到1,剩余匹配数size -= 1
                if (n === 0) {
                    size += 1
                }

            }
            l++;
        }
        r++;
    }
    return res;
};

console.log(minWindow(s, t))

5. 关键字

滑动窗口