【 每天学习一点算法 2026/05/08】最小覆盖子串

每天学习一点算法 2026/05/08

题目:最小覆盖子串

给定两个字符串 s 和 t,长度分别是 m 和 n,返回 s 中的 最短窗口 子串,使得该子串包含 t 中的每一个字符(包括重复字符)。如果没有这样的子串,返回空字符串 ""。

测试用例保证答案唯一。

作者:LeetCode

链接:https://leetcode.cn/leetbook/read/top-interview-questions-hard/xw1tws/

来源:力扣(LeetCode)

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这个题需要用到滑动窗口,我们可以维护两个指针 leftright 分别指向窗口两个端点,循环判断当窗口内的子串能覆盖目标值时,left 右移,当窗口内子串不能覆盖目标值时, right右移,每次满足覆盖条件式记录最小值,直到 right 指向 s 的最右侧且窗口内子串不满足覆盖条件时,结束循环。

由于字符串 t 可能会有重复字符,所以我们需要维护两个 map 来统计窗口内字符串的字符数量和目标字符串 t 的字符数量。

typescript 复制代码
function minWindow(s: string, t: string): string {
  // 检查当前窗口是否满足条件
  function check() {
    for (const [char, count] of mapT) {
      if (!mapS.has(char) || mapS.get(char)! < count) {
        return false;
      }
    }
    return true;
  }

  const mapS = new Map<string, number>(); // 窗口内字符计数
  const mapT = new Map<string, number>(); // t 字符计数

  // 初始化 t 的频率表
  for (const char of t) {
    mapT.set(char, (mapT.get(char) || 0) + 1);
  }

  let left = 0, min = Infinity, startL = -1;

  // 右指针遍历
  for (let right = 0; right < s.length; right++) {
    // 不断移动右指针并记录窗口内字符频率表
    const charR = s[right];
    mapS.set(charR, (mapS.get(charR) || 0) + 1);

    // 满足条件就收缩左指针
    while (check()) {
      // 更新最小窗口
      if (right - left + 1 < min) {
        min = right - left + 1;
        startL = left;
      }
      // 左指针右移
      const charL = s[left];
      mapS.set(charL, mapS.get(charL)! - 1);
      left++;
    }
  }

  return startL === -1 ? '' : s.substring(startL, startL + min);
}

上面的这个方法时间复杂度高主要是 check 函数造成的,因为每次都需要遍历一次 mapT 来判断是否满足条件,这里我们其实可以用维护一个变量 match 来统计 窗口中字符匹配目标字符串的字符及其个数的,也就是 t 中的字符在 s 中存在且数量一致时 match 数量才加一,当 match 和 mapT.size 相等时就表示满足覆盖条件了,这样我们就可以进一步优化时间复杂度

typescript 复制代码
function minWindow(s: string, t: string): string {
  const need = new Map<string, number>(); // 存储需要匹配的字符及其个数
  const window = new Map<string, number>(); // 存储窗口中的字符及其个数

  // 统计 t 中字符及其个数
  for (const c of t) need.set(c, (need.get(c) || 0) + 1);

  let left = 0, right = 0;
  let match = 0; // 匹配成功的字符种类数
  let start = 0, minLen = Infinity;
  
  // 循环直至右指针到达字符串 s 右边界
  while (right < s.length) {
    const c = s[right]; // 记录窗口新增字符
    right++; // 移动有指针

    // 更新窗口内字符统计
    if (need.has(c)) {
      window.set(c, (window.get(c) || 0) + 1);
      // 如果当前字符满足匹配条件 match 加一
      if (window.get(c) === need.get(c)) match++;
    }

    // 当 match 和 need.size 相等表示满足覆盖条件,收缩窗口
    while (match === need.size) {
      // 更新最小窗口
      if (right - left < minLen) {
        minLen = right - left;
        start = left;
      }

      const d = s[left];
      left++;

      // 更新窗口内字符统计
      if (need.has(d)) {
        // 如果是需匹配字符串的字符需要更新 match
        if (window.get(d) === need.get(d)) match--;
        // 窗口内该字符数量减一
        window.set(d, window.get(d)! - 1);
      }
    }
  }

  return minLen === Infinity ? '' : s.substr(start, minLen);
}

题目来源:力扣(LeetCode)

相关推荐
元气少女小圆丶8 分钟前
SenseGlove Nova 2+Unity开发笔记1
笔记·学习·unity
QiLinkOS15 分钟前
【从实验室到商业战场:发明专利如何重塑科技与企业的共生生态】
大数据·c语言·数据结构·c++·人工智能·单片机·算法
nashane43 分钟前
HarmonyOS 6学习:应用退出动画优化实战——从“闪退“到优雅退出的完美蜕变
学习·华为·harmonyos
小白兔奶糖ovo1 小时前
【Leetcode】231. 2的幂
linux·算法·leetcode
xiaoxiaoxiaolll1 小时前
《Light: Science & Applications》合并BIC实现80倍阈值单模运行:超紧凑光子晶体激光器新突破
人工智能·算法·机器学习
Peter·Pan爱编程1 小时前
14. Lambda 表达式:随手可写的函数对象
c++·算法·ai编程
-To be number.wan1 小时前
算法日记 | 暴力枚举
学习·算法
s_w.h2 小时前
【 linux 】动静态库的制作
linux·运维·服务器·算法·bash
过期动态2 小时前
【LeetCode 热题 100】接雨水
java·数据结构·算法·leetcode·职场和发展
春日见2 小时前
5分钟入门强化学习之动态规划算法与实现
大数据·人工智能·python·算法·机器学习·计算机视觉