【每日算法】LeetCode 76. 最小覆盖子串

对前端开发者而言,学习算法绝非为了"炫技"。它是你从"页面构建者"迈向"复杂系统设计者"的关键阶梯。它将你的编码能力从"实现功能"提升到"设计优雅、高效解决方案"的层面。从现在开始,每天投入一小段时间,结合前端场景去理解和练习,你将会感受到自身技术视野和问题解决能力的质的飞跃。------ 算法:资深前端开发者的进阶引擎

LeetCode 76. 最小覆盖子串

1. 题目描述

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

注意:

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

示例:

  • 输入:s = "ADOBECODEBANC", t = "ABC"
  • 输出:"BANC"
  • 解释:最小子串 "BANC" 包含 'A'、'B' 和 'C'。

2. 问题分析

这是一个典型的子串覆盖问题 ,需要从字符串 s 中找到最短的连续子串,使得该子串包含字符串 t 的所有字符(包括重复字符)。问题本质是优化搜索过程,避免枚举所有子串。

前端视角:在前端开发中,类似场景包括用户输入过滤(如搜索框自动完成)、文本高亮匹配或动态内容渲染,其中需要高效处理字符串以提供实时响应。

3. 解题思路

3.1 暴力法(Brute Force)

枚举 s 的所有子串,检查每个子串是否覆盖 t,并记录最小长度。这种方法简单但效率低下,不适用于长字符串。

复杂度:

  • 时间复杂度:O(n^3),其中 n 是 s 的长度(枚举子串 O(n^2),检查覆盖 O(n))。
  • 空间复杂度:O(m),用于存储 t 的字符计数,m 是 t 中字符集大小。

3.2 滑动窗口法(Sliding Window,最优解)

使用两个指针 leftrights 上定义窗口,通过移动指针动态调整窗口大小。用哈希表记录字符需求,确保窗口覆盖 t 的所有字符。

步骤:

  1. 初始化哈希表 need,记录 t 中每个字符所需数量。
  2. 使用 left = 0right = 0 滑动窗口,valid 计数满足需求的字符种类数。
  3. 扩展 right 指针,增加窗口,直到窗口覆盖 t
  4. 收缩 left 指针,缩小窗口,同时更新最小子串。
  5. 重复直到 right 到达 s 末尾。

复杂度:

  • 时间复杂度:O(n),其中 n 是 s 的长度,每个字符最多被访问两次。
  • 空间复杂度:O(m),用于哈希表存储,m 是字符集大小(如 ASCII 为 128)。

最优解:滑动窗口法是最优解,因为它在线性时间内解决问题。

4. 各思路代码实现

4.1 暴力法实现(JavaScript)

javascript 复制代码
function minWindowBruteForce(s, t) {
    if (s.length < t.length) return "";
    
    const need = new Map();
    for (let char of t) {
        need.set(char, (need.get(char) || 0) + 1);
    }
    
    let minLen = Infinity, minStr = "";
    
    for (let i = 0; i < s.length; i++) {
        for (let j = i + t.length; j <= s.length; j++) {
            const substr = s.substring(i, j);
            if (isCover(substr, new Map(need))) {
                if (substr.length < minLen) {
                    minLen = substr.length;
                    minStr = substr;
                }
                break; // 找到覆盖就跳出内层循环,因为继续扩展只会更长
            }
        }
    }
    
    return minStr;
}

function isCover(substr, need) {
    for (let char of substr) {
        if (need.has(char)) {
            need.set(char, need.get(char) - 1);
            if (need.get(char) === 0) need.delete(char);
        }
    }
    return need.size === 0;
}

// 测试
console.log(minWindowBruteForce("ADOBECODEBANC", "ABC")); // 输出 "BANC"

4.2 滑动窗口法实现(JavaScript,最优解)

javascript 复制代码
function minWindow(s, t) {
    if (s.length < t.length) return "";
    
    const need = new Map();
    const window = new Map();
    for (let char of t) {
        need.set(char, (need.get(char) || 0) + 1);
    }
    
    let left = 0, right = 0;
    let valid = 0; // 满足需求的字符种类数
    let start = 0, minLen = Infinity;
    
    while (right < s.length) {
        const char = s[right];
        right++;
        
        // 更新窗口数据
        if (need.has(char)) {
            window.set(char, (window.get(char) || 0) + 1);
            if (window.get(char) === need.get(char)) {
                valid++;
            }
        }
        
        // 当窗口覆盖 t 时,收缩左侧
        while (valid === need.size) {
            // 更新最小子串
            if (right - left < minLen) {
                minLen = right - left;
                start = left;
            }
            
            const leftChar = s[left];
            left++;
            
            if (need.has(leftChar)) {
                if (window.get(leftChar) === need.get(leftChar)) {
                    valid--;
                }
                window.set(leftChar, window.get(leftChar) - 1);
            }
        }
    }
    
    return minLen === Infinity ? "" : s.substring(start, start + minLen);
}

// 测试
console.log(minWindow("ADOBECODEBANC", "ABC")); // 输出 "BANC"
console.log(minWindow("a", "aa")); // 输出 ""

5. 各实现思路的复杂度、优缺点对比表格

思路 时间复杂度 空间复杂度 优点 缺点 适用场景
暴力法 O(n^3) O(m) 实现简单,易于理解 效率极低,不适用于长字符串 小规模数据或教学示例
滑动窗口法 O(n) O(m) 高效,线性时间解决 实现稍复杂,需维护状态 实际应用,如前端实时处理

6. 总结

LeetCode 76. 最小覆盖子串问题展示了滑动窗口算法的强大之处,它将复杂问题优化到线性时间。对于前端开发者,掌握此类算法不仅提升面试竞争力,更能应用于实际场景:

  • 实际应用场景
    • 搜索框自动完成:快速匹配用户输入的子串,提供实时建议。
    • 文本编辑器高亮:在长文档中高效查找并高亮关键词。
    • 数据过滤:在前端处理大型列表或字符串数据时,动态过滤覆盖特定模式的内容。
    • 性能优化:减少不必要的DOM操作或计算,确保用户界面流畅响应。
相关推荐
LXS_3577 小时前
Day17 C++提高 之 类模板案例
开发语言·c++·笔记·算法·学习方法
小年糕是糕手7 小时前
【C++】string类(一)
linux·开发语言·数据结构·c++·算法·leetcode·改行学it
初願致夕霞8 小时前
LeetCode双指针题型总结
算法·leetcode·职场和发展
努力学算法的蒟蒻8 小时前
day36(12.17)——leetcode面试经典150
算法·leetcode·面试
sali-tec8 小时前
C# 基于halcon的视觉工作流-章70 深度学习-Deep OCR
开发语言·人工智能·深度学习·算法·计算机视觉·c#·ocr
绿算技术8 小时前
在稀缺时代,定义“性价比”新标准
大数据·数据结构·科技·算法·硬件架构
zore_c8 小时前
【C语言】贪吃蛇游戏超详解(包含音效、颜色、封装成应用等)
c语言·数据结构·笔记·stm32·游戏·链表
一起养小猫8 小时前
《Java数据结构与算法》第四篇(二)二叉树的性质、定义与链式存储实现
java·数据结构·算法
乌萨奇也要立志学C++8 小时前
【洛谷】贪心专题之哈夫曼编码 从原理到模板题解析
c++·算法