最小覆盖子串

最小覆盖子串

CategoryDifficultyLikesDislikes
algorithmsHard (43.18%)1565-

Tags

hash-table | two-pointers | string | sliding-window

Companies

facebook | linkedin | snapchat | uber

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

注意:

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

示例 1:

1
2
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"

示例 2:

1
2
输入:s = "a", t = "a"
输出:"a"

示例 3:

1
2
3
4
输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。

提示:

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

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


Discussion | Solution

解法

滑动窗口

  • 右指针 r: 当窗口未完整包含目标字符串 t 时,r 右移,增大窗口范围;
  • 左指针 l: 当窗口已经完整包含目标字符串 t 时,l 右移,减小窗口范围,获取包含目标字符串最小长度;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// @lc code=start
impl Solution {
    /// ## 解题思路
    /// - 滑动窗口 + hashmap(可用固定数组代替)
    /// 1. 窗口右边界滑动时,检查当前字符在窗口内出现的次数是否达到目标字符串;
    /// 2. 如果达到,则增加有效计数;
    /// 3. 检查窗口左边界字符出现次数是否超过目标字符串中字符次数, 如果超过,则右滑左边界;
    /// 4. 检查有效计数是否达到目标字符串大小,如果达到,则更新结果字符串;
    pub fn min_window(s: String, t: String) -> String {
        let mut min_substr = "";

        let mut t_stat = vec![0; 60]; // 目标字符串字符频次统计
        let mut w_stat = vec![0; 60]; // 滑动窗口内字符频次统计

        // 统计目标字符串中各字符次数
        t.bytes().map(|b| (b - b'A') as usize).for_each(|id| {
            t_stat[id] += 1;
        });

        let mut sb = s.as_bytes();
        let mut valid_count = 0; //窗口内有效字符计数
        let mut l = 0; // 滑动窗口左右边界
        for r in 0..sb.len() {
            let ri = (sb[r] - b'A') as usize;
            // 增加滑动窗口内部右边界字符频次统计
            w_stat[ri] += 1;
            // 如果当前字符不在目标字符串内
            if t_stat[ri] == 0 {
                continue;
            }
            // 如果当前字符频次未超过目标字符串内字符频次
            if w_stat[ri] <= t_stat[ri] {
                valid_count += 1; // 有效字符计数递增
            }
            // 处理滑窗左边界
            while l < r {
                let li = (sb[l] - b'A') as usize;
                // 如果窗口左边界字符频次 超过 目标字符频次
                if w_stat[li] > t_stat[li] {
                    l += 1; // 滑动左边界
                    w_stat[li] -= 1; // 滑窗中的字符次数递减
                } else {
                    break;
                }
            }

            // 如果当前窗口内有效字符数 == 目标字符数
            if valid_count == t.len() {
                // 如果
                if min_substr.is_empty() || r - l + 1 < min_substr.len() {
                    min_substr = &s[l..=r];
                }
            }
        }

        min_substr.to_string()
    }
}
// @lc code=end

struct Solution;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
public:
    /*
    * ## 解题思路
    * * 滑动窗口
    * * 右指针r: 如果当前窗口没有完整包含t,则r右移,扩大窗口范围,直到窗口完整包含t;
    * * 左指针l: 如果当前已经完整包含t,则l右移,减小窗口范围,直到窗口为完整包含t的最小窗口;
    * *
    */
    string minWindow(string s, string t) {
        string res;   //结果
        unordered_map<char, int> s_map;   //当前窗口内字符数统计
        unordered_map<char, int> t_map;   //目标字符数统计
        int valid_count = 0;      //当前窗口内的有效字符数

        // 初始化目标hash数组
        for(auto c: t) t_map[c]++;
        //
        for(int r=0, l=0; r<s.length(); r++) {
            s_map[s[r]]++;     //当前窗口内字符数+1;
            //当前字符统计数未超过目标字符统计数是,
            if(s_map[s[r]] <= t_map[s[r]]) valid_count++;
            // 当前窗口内左字符有效字符数>目标字符数,滑动左指针
            while(s_map[s[l]] > t_map[s[l]]) s_map[s[l++]]--;
            // 刚好
            if (valid_count == t.length()) {
                if(res.empty() || r+1-l < res.size()) {
                    res=s.substr(l, r+1-l);
                }
            }
        }
       return res;
    }
};
updatedupdated2024-08-252024-08-25