i007.cc

i007.cc

优先队列-降维打击

05.价值资料

最小覆盖子串(Minimum Window Substring)

力扣第 76 题。给定字符串 st,找 s包含 t 所有字符的最短子串


核心思路:滑动窗口 + 计数器

还是滑动窗口,但比上题多一个难点——需要知道窗口是否已经”覆盖”了 t 的所有字符。

关键在于设计一个 need 变量来追踪”还缺几种字符”,避免每次都遍历整个哈希表来判断是否满足条件。


最优解法:O(n) 滑动窗口

class Solution {
public:
    string minWindow(string s, string t) {
        int freq[128] = {}; // 记录 t 中各字符还需要多少个
        for (char c : t)
            freq[c]++;

        int need = t.size(); // 还需要凑齐多少个字符(含重复)
        int left = 0;
        int ans_start = 0, ans_len = INT_MAX;

        for (int right = 0; right < s.size(); right++) {
            // 1. 右边字符入窗口
            char rc = s[right];
            if (freq[rc] > 0)
                need--; // 这个字符是 t 还缺的,need--
            freq[rc]--;

            // 2. 窗口已覆盖 t,尝试收缩左边
            while (need == 0) {
                // 更新最优解
                if (right - left + 1 < ans_len) {
                    ans_len = right - left + 1;
                    ans_start = left;
                }
                // 左边字符出窗口
                char lc = s[left];
                freq[lc]++;
                if (freq[lc] > 0)
                    need++; // 这个字符重新变成缺的,need++
                left++;
            }
        }

        return ans_len == INT_MAX ? "" : s.substr(ans_start, ans_len);
    }
};

 


理解两个关键设计

freq 数组的含义随着窗口变化

初始时 freq[c] 是 t 中 c 的需求量。窗口右扩时 freq[rc]--,左缩时 freq[lc]++。所以它始终代表当前窗口相对于 t 的净缺口

  • freq[c] > 0:窗口还欠 t 中 c 的字符
  • freq[c] <= 0:窗口中 c 已经够了(甚至多余)

need 是精髓

need 记录的是”还有几个字符位置未被满足”。只有当 freq[c] 从 1 变到 0(正好补上缺口)时,need-- 才有意义;从 0 变到 -1 是多余的字符,不改变 need。这样 need == 0 就精确等价于”窗口已完全覆盖 t”,判断 O(1) 完成。


完整走一遍例子

s = "ADOBECODEBANC", t = "ABC"
初始: freq = {A:1, B:1, C:1}, need = 3

right 扩张直到 need == 0(找到第一个覆盖窗口 "ADOBEC")
→ 开始收缩 left,更新最优解
→ left 移到 'D' 时,A 从窗口移出,need 变回 1
→ right 继续扩张,找到下一个覆盖窗口 "DOBECODEBA"...
→ 最终最优解是 "BANC",长度 4

复杂度

维度 复杂度 说明
时间 O(n + m) n = |s|,m = |t|,每个字符最多进出窗口各一次
空间 O(∣Σ∣) 字符集大小,ASCII 下固定 128

和上题的对比

最长不重复子串 最小覆盖子串
窗口目标 无重复字符 覆盖 t 所有字符
收缩时机 有重复就收缩 满足条件后尽量收缩
判断条件 集合 contains need == 0
求的是 最长 最短

三道题(接雨水、最长不重复、最小覆盖子串)连起来看,会发现双指针/滑动窗口的本质是:用单调性把 O(n²) 暴力枚举压缩成 O(n)——这是面试中最值得讲清楚的一条主线。

发表回复