2026/5/20 14:47:19
网站建设
项目流程
网站开发需求收集 模板,做网站的公司怎么推广,凡科做网站,网站ip屏蔽引言#xff1a;为什么这道题是算法面试高频题#xff1f;“最小覆盖子串”#xff08;LeetCode 76#xff09;是字符串处理领域的经典难题#xff0c;也是大厂面试中高频出现的算法题。它的核心考点是滑动窗口#xff08;双指针#xff09; 与哈希表的结合运用#xf…引言为什么这道题是算法面试高频题“最小覆盖子串”LeetCode 76是字符串处理领域的经典难题也是大厂面试中高频出现的算法题。它的核心考点是滑动窗口双指针与哈希表的结合运用不仅要求开发者能写出正确代码更需要理解 “动态窗口优化” 的底层逻辑 —— 如何在线性时间内找到 “最小且全覆盖” 的子串避免暴力枚举的低效陷阱。如果你曾被这道题的复杂逻辑绕晕或想掌握滑动窗口的通用解题框架本文将从问题分析、思路设计、代码拆解到实战延伸带你一步步吃透这道题。一、问题重述与难点分析1. 问题定义给定两个字符串 s源字符串和 t目标字符串在 s 中找到包含t所有字符的最小子串要求子串中 t 的每个字符出现次数 ≥ 其在 t 中的出现次数若不存在这样的子串返回空字符串 。示例输入s ADOBECODEBANC, t ABC输出BANC长度为 4是所有满足条件的子串中最短的2. 核心难点如何快速判断 “窗口是否覆盖 t 所有字符”避免逐一比对字符频次如何在找到有效窗口后快速收缩到 “最小长度”避免冗余字符占用窗口空间如何保证时间复杂度最优暴力枚举 \(O(n^2)\) 会超时需优化到线性时间二、解题思路滑动窗口 哈希表的黄金组合1. 滑动窗口的核心逻辑滑动窗口本质是用两个指针左指针 i、右指针 j维护一个动态区间 [i, j]通过 “扩张 - 收缩” 两个动作寻找最优解扩张右指针 j 向右移动将字符加入窗口直到窗口满足 “覆盖 t 所有字符”有效窗口收缩左指针 i 向右移动移除窗口内的冗余字符非 t 字符或频次超过需求的 t 字符直到窗口不再满足条件此时记录当前最小窗口重复 “扩张 - 收缩”直到右指针遍历完 s。2. 哈希表的优化设计常规思路是用两个哈希表一个存 t 的字符需求一个存窗口内字符频次但这样会增加空间开销和比对成本。本文代码采用单哈希表ASCII 数组实现双重功能是核心优化点数组长度设为 128覆盖所有 ASCII 字符初始值为 0遍历 t 时将对应字符的哈希值减 1用负数标记 “需要的字符及次数”例如 tAAB则 hash[A]-2hash[B]-1遍历 s 时将窗口内字符的哈希值加 1通过哈希值的正负 / 大小判断字符状态哈希值状态含义字符是 t 所需且窗口内数量未满足需求 0该字符是 t 所需且窗口内数量刚好满足需求 0该字符是冗余窗口内数量超过需求或非 t 字符3. 关键变量设计count记录窗口中 “已满足需求的 t 字符种类数”例如 tABC当窗口中同时有 A、B、C 且数量达标时count3minLen存储最小窗口的长度初始化为 s.length()1确保初始无有效窗口时后续可更新subLeft存储最小窗口的左边界初始化为 -1标记无有效窗口。三、代码逐行拆解带注释 逻辑分析class Solution {public:string minWindow(string s, string t) {// 1. 初始化哈希表128位ASCII数组同时记录t的需求负数和窗口频次动态更新vectorint hash(128, 0);for (char c : t) hash[c]--; // 标记t中字符的需求需要k个则为-kint sLen s.length(), tLen t.length();int count 0; // 已满足需求的t字符种类数达到t中该字符的需求次数int minLen sLen 1; // 最小窗口长度初始值大于s的最大可能长度int subLeft -1; // 最小窗口的左边界初始为-1表示无有效窗口// 2. 滑动窗口核心循环j是右指针扩张窗口i是左指针收缩窗口for (int i 0, j 0; j ; j) {char currentChar s[j]; // 当前加入窗口的字符// 2.1 判断当前字符是否是t所需且未满足需求if (hash[currentChar] 0) {count; // 满足一种字符的需求count加1}hash[currentChar]; // 窗口中该字符频次1无论是否是t的字符// 2.2 收缩窗口移除冗余字符hash[s[i]]0表示冗余while (i j hash[s[i]] 0) {hash[s[i]]--; // 冗余字符频次减1i; // 左指针右移收缩窗口}// 2.3 更新最小窗口仅当窗口满足覆盖t所有字符counttLen且长度更小时if (count tLen j - i 1 Len) {minLen j - i 1; // 更新最小长度subLeft i; // 更新最小窗口左边界}}// 3. 返回结果有有效窗口则截取子串否则返回空字符串return subLeft 0 ? s.substr(subLeft, minLen) : ;}};关键步骤深度解析步骤 1哈希表初始化hash[c]--这一步是代码的 “巧思核心”。例如 tABC遍历后 hash[A]-1、hash[B]-1、hash[C]-1其他字符为 0。这样设计的好处是无需额外维护 “需求表” 和 “窗口表”一个数组搞定两类信息后续通过 hash[c] 的正负可直接判断字符是否是 t 所需、是否满足需求。步骤 2右指针扩张j每加入一个字符 s[j]先判断是否是 t 所需且未满足hash[c]若是则 count 加 1表示多满足一种字符的需求无论是否是 t 的字符都要 hash[c]更新窗口内字符频次为后续收缩窗口做准备。步骤 3左指针收缩i当 hash[s[i]]0 时说明 s[i] 是冗余字符可能是 t 的字符但频次超了也可能是非 t 字符此时收缩窗口左指针右移移除该冗余字符同步减少 hash[s[i]] 的值保持频次准确收缩过程直到窗口无冗余字符hash[s[i]]确保窗口是 “当前右边界下的最小有效窗口”。步骤 4更新最小窗口只有当 counttLen窗口覆盖 t 所有字符时才判断当前窗口是否是最小的若当前窗口长度 j-i1 小于 minLen则更新 minLen 和 subLeft这里无需担心后续会有更小的窗口因为收缩过程已保证当前窗口是 “最小有效”后续右指针继续扩张时会优先收缩冗余再判断是否更新。四、时间 / 空间复杂度分析1. 时间复杂度\(O(n)\)\(n\) 是 s 的长度右指针 j 遍历 s 一次\(O(n)\)左指针 i 最多收缩 n 次每个字符最多被移除一次\(O(n)\)总操作次数为 \(O(n) O(n) O(n)\)属于线性时间效率远超暴力枚举。2. 空间复杂度\(O(1)\)哈希表是固定长度的数组128 个元素与输入字符串长度无关其他变量count、minLen 等均为常数级空间因此空间复杂度为常数级 \(O(1)\)。五、实战技巧与避坑指南1. 常见错误点忘记 count 的作用直接比对哈希表所有元素是否 ≥0会导致时间复杂度升高\(O(128n)\)虽然仍是线性但效率下降收缩窗口时未判断 i 左指针超过右指针无效窗口初始 minLen 设为 s.length()当 s 本身就是最小窗口时会无法更新应设为 s.length()1。2. 同类题延伸滑动窗口通用思路掌握本题的 “滑动窗口 哈希表” 框架后可直接解决以下高频题LeetCode 3最长无重复子串用哈希表记录字符最后出现位置收缩窗口时直接跳到重复字符后LeetCode 438找到字符串中所有字母异位词窗口长度固定为 t.length()只需判断窗口内字符频次是否与 t 一致LeetCode 567字符串的排列与 438 类似窗口长度固定判断是否是 t 的排列。这些题的核心共性用滑动窗口维护动态区间用哈希表快速判断区间是否满足条件。六、总结“最小覆盖子串” 的解题关键的是用单哈希表同时记录字符需求和窗口频次通过正负值简化判断滑动窗口的 “扩张 - 收缩” 逻辑右指针找有效窗口左指针优化最小窗口用 count 变量快速判断窗口是否覆盖 t 所有字符避免冗余比对。这道题的优化思路体现了算法设计的 “极致性”—— 用最少的空间、最低的时间复杂度解决问题。掌握后你不仅能搞定这道题更能举一反三应对所有滑动窗口类问题。如果在刷题过程中遇到其他疑问或想深入探讨滑动窗口的更多应用场景欢迎在评论区交流