当前位置: 首页 > article >正文

【码道初阶-Hot100】LeetCode 438 + 567 对照详解:一套滑动窗口模板,彻底讲透“固定长度窗口 + 计数数组 + count维护”

LeetCode 438 567 对照详解一套滑动窗口模板彻底讲透“固定长度窗口 计数数组 count维护”摘要很多人把 LeetCode 438 和 567 当成两道题分开记其实完全没必要。它们本质上是同一个固定长度滑动窗口模型真正难点只在一个地方count如何维护以及为什么出窗口必须“先判后减”。这篇文章就把这两题放在一起一次讲透。LeetCode 438. 找到字符串中所有字母异位词和LeetCode 567. 字符串的排列是两道极其相似的题。很多人会觉得它们是两道不同题但实际上它们的核心思路几乎一模一样都是在大串里找一个长度固定的窗口都是在判断这个窗口是否和目标串字符频次一致都可以用“计数数组 滑动窗口 count 维护有效字符数”来解决两题真正的区别只有一个438要把所有合法窗口的起点都记录下来567只要发现一个合法窗口就立刻返回true这篇文章就把这两题放在一起讲清楚重点解决下面几个常见疑问为什么这两题本质上是同一个模型count到底在统计什么为什么“进窗口先加后判出窗口先判后减”为什么这个顺序一旦写反就会出 bug这两题代码到底该怎么统一记忆目录文章目录LeetCode 438 567 对照详解一套滑动窗口模板彻底讲透“固定长度窗口 计数数组 count维护”摘要目录一、先看两道题分别在问什么LeetCode 438找到字符串中所有字母异位词LeetCode 567字符串的排列二、这两题本质上是同一题三、为什么一定是“固定长度滑动窗口”1. 长度相同2. 字符种类与数量完全相同四、统一建模两个计数数组1. need2. window五、进一步优化为什么要引入 count六、count 到底在统计什么例子 1例子 2七、进窗口时为什么是“先加后判”八、出窗口时为什么是“先判后减”九、最容易翻车的地方为什么顺序写反会错十、用一个经典反例说明 bugs aaab, p ab错误逻辑会怎么出问题十一、所以这类题的万能口诀就是进窗口出窗口十二、LeetCode 438 标准代码详细注释版十三、LeetCode 567 标准代码详细注释版十四、把 438 和 567 放在一起看差别到底只有哪一点438567所以这两题的本质区别就是十五、这两题能不能直接比较两个数组而不用 count十六、统一模板版写法十七、复杂度分析时间复杂度空间复杂度十八、面试高频追问总结1. 为什么这两题可以用同一套模板2. count 到底统计什么3. 为什么 438 和 567 代码几乎一样4. 为什么出窗口必须先判后减5. 怎么快速记住这类题十九、学习路线总结第一步先理解固定长度窗口第二步理解两个计数数组第三步彻底理解 count第四步记住状态维护顺序第五步分清两题唯一差别二十、结语一、先看两道题分别在问什么LeetCode 438找到字符串中所有字母异位词给定字符串s和p要求找出s中所有是p的字母异位词的子串起始下标。例如scbaebabacdpabc输出[0,6]因为s[0..2] cba是abc的异位词s[6..8] bac也是abc的异位词LeetCode 567字符串的排列给定字符串s1和s2判断s2中是否包含s1的某个排列。例如s1abs2eidbaooo输出true因为s2中包含ba它是ab的一个排列。二、这两题本质上是同一题把两道题抽象一下会发现它们都在做同一件事在大字符串中寻找长度等于目标串长度的窗口判断该窗口的字符频次是否和目标串一致。也就是说目标串长度固定窗口长度固定窗口不断右移每次判断当前窗口是否“匹配目标串的字符需求”所以它们实际上是同一个固定长度滑动窗口模型。三、为什么一定是“固定长度滑动窗口”这是两题最根本的共同点。如果一个字符串是另一个字符串的字母异位词那么它们一定满足1. 长度相同例如abc和cba长度都为 3。2. 字符种类与数量完全相同只是顺序不同。所以在大串中找异位词/排列时根本不需要考虑任意长度窗口而只需要考虑长度等于目标串长度的窗口这就是为什么这两题都天然适合固定长度滑动窗口。四、统一建模两个计数数组这两题都可以定义两个数组1.need记录目标串中每个字符需要多少个。2.window记录当前窗口中每个字符出现多少个。例如paab那么need[a]2need[b]1当窗口中字符统计刚好和need一致时说明当前窗口就是一个合法答案。五、进一步优化为什么要引入count最直接的做法是每次窗口滑动后都比较一次need和window是否完全相等因为字符集只有 26 个小写字母这样其实也能过。但更常见、也更适合面试表达的方式是用一个count来维护当前窗口中“已经有效匹配上的字符总数”这样每次就不用反复比较两个数组了。六、count到底在统计什么这是这套模板最容易理解错的地方。count统计的不是匹配了多少种字符而是匹配了多少个字符举个例子。例子 1pabc如果当前窗口是abx那么a有效b有效x无效所以count2例子 2paab如果当前窗口是aab那么两个a都有效一个b有效所以count3注意这里不是 2而是 3。因为count统计的是有效字符总数不是字符种类数。七、进窗口时为什么是“先加后判”假设当前右指针加入了一个字符c。正确写法是window[c];if(window[c]need[c]){count;}为什么因为判断的是这个字符加入窗口之后它是否仍然处于需求范围内。如果加入后没有超标那说明这次加入的是一个有效字符于是count。所以进窗口的正确顺序是先加桶再判断要不要 count这可以记成一句话进窗口先加后判八、出窗口时为什么是“先判后减”假设当前左边要移出一个字符c。正确写法是if(window[c]need[c]){count--;}window[c]--;为什么因为判断的是这个字符离开窗口之前它是否正在为count作贡献。如果离开前它是有效字符那么它离开后count就必须减 1。但如果它离开前其实是“多余字符”那它本来就没在贡献count自然不能减。所以出窗口的正确顺序是先判断要不要 count–再减桶这可以记成一句话出窗口先判后减九、最容易翻车的地方为什么顺序写反会错这个 bug 非常经典。假设错误写法是window[c]--;if(window[c]need[c]){count--;}问题在于某个字符原本可能是“多余的”它移出后桶值刚好回到合法区间于是程序误以为“移出去的是有效字符”错误地执行了count--但实际上从“超标”减到“刚好合法”说明移出去的是多余字符它本来就没有给count作贡献。所以这种写法会把count维护错。十、用一个经典反例说明 bugs aaab, p ab目标需求a:1b:1窗口长度m2正确答案是[2]因为ab是合法异位词窗口。错误逻辑会怎么出问题当窗口从aab收缩为ab时移出去的是一个“多余的 a”。这个a本来就不该影响count。但如果写成window[a]--;if(window[a]need[a]){count--;}那么原本window[a] 2移出后变成11 1成立程序就错误执行了count--结果合法窗口ab被错判掉。十一、所以这类题的万能口诀就是对于这种“固定长度窗口 count维护有效字符数”的题务必记住进窗口先加后判出窗口先判后减也就是进window再判断 count出先判断 count--再 window--这句口诀对于438和567都通用。十二、LeetCode 438 标准代码详细注释版importjava.util.*;classSolution{publicListIntegerfindAnagrams(Strings,Stringp){ListIntegerresnewArrayList();intmp.length();// need 记录目标串 p 的字符需求int[]neednewint[26];for(charc:p.toCharArray()){need[c-a];}// window 记录当前滑动窗口中的字符统计int[]windownewint[26];intleft0;intcount0;// right 作为窗口右边界不断扩张for(intright0;rights.length();right){intidxRs.charAt(right)-a;// 进窗口先加桶window[idxR];// 如果加入后该字符仍在需求范围内说明它是有效字符if(window[idxR]need[idxR]){count;}// 保证窗口长度不超过 mif(right-left1m){intidxLs.charAt(left)-a;// 出窗口先判断这个字符离开前是否有效if(window[idxL]need[idxL]){count--;}// 再真正移出窗口window[idxL]--;left;}// 如果有效字符总数刚好等于 m// 说明当前长度为 m 的窗口完全匹配 pif(countm){res.add(left);}}returnres;}}十三、LeetCode 567 标准代码详细注释版这题和 438 唯一的区别就是438把所有合法窗口的起点收集起来567只要找到一个合法窗口立刻返回trueclassSolution{publicbooleancheckInclusion(Strings1,Strings2){intms1.length();// 如果目标串比原串还长不可能存在排列if(ms2.length())returnfalse;int[]neednewint[26];for(charc:s1.toCharArray()){need[c-a];}int[]windownewint[26];intleft0;intcount0;for(intright0;rights2.length();right){intidxRs2.charAt(right)-a;// 进窗口先加后判window[idxR];if(window[idxR]need[idxR]){count;}// 保证窗口长度不超过 mif(right-left1m){intidxLs2.charAt(left)-a;// 出窗口先判后减if(window[idxL]need[idxL]){count--;}window[idxL]--;left;}// 只要发现一个合法窗口就返回 trueif(countm){returntrue;}}returnfalse;}}十四、把 438 和 567 放在一起看差别到底只有哪一点其实模板完全一样唯一差别就在最后一步。438找到合法窗口后res.add(left);因为要记录所有答案。567找到合法窗口后returntrue;因为只要存在一个就行。所以这两题的本质区别就是438求所有567求是否存在这也是为什么很多刷题经验里会把这两题当作“同模板题”。十五、这两题能不能直接比较两个数组而不用count可以。例如每次窗口长度等于m时直接比较Arrays.equals(need,window)也能做出来而且因为数组长度固定为 26时间也不算大。不过如果是从“模板”和“面试表达”的角度来看count写法更值得掌握因为它更有迁移性后面很多滑动窗口题都能复用这套思维。十六、统一模板版写法如果只想记一个最核心的模板可以记成下面这样。int[]neednewint[26];int[]windownewint[26];intleft0,count0;for(intright0;rights.length();right){intidxRs.charAt(right)-a;// 进窗口先加后判window[idxR];if(window[idxR]need[idxR]){count;}// 超长就收缩if(right-left1targetLen){intidxLs.charAt(left)-a;// 出窗口先判后减if(window[idxL]need[idxL]){count--;}window[idxL]--;left;}// 判断当前窗口是否满足条件if(counttargetLen){// 438记录答案// 567直接返回 true}}这就是这两题共同的核心模板。十七、复杂度分析两题复杂度完全一样。时间复杂度右指针从左到右扫描一遍左指针也只会单调右移所以总时间复杂度是O(n)其中n是大串长度。空间复杂度只用了若干长度为 26 的数组O(1)因为字符集固定。十八、面试高频追问总结1. 为什么这两题可以用同一套模板因为它们本质都是“固定长度窗口中判断字符频次是否匹配目标串”。2.count到底统计什么统计的是“窗口中有效匹配的字符总数”不是字符种类数。3. 为什么 438 和 567 代码几乎一样因为它们的判断条件和窗口维护方式完全一致只是最终返回结果不同。4. 为什么出窗口必须先判后减因为要判断的是字符离开窗口之前是否有效而不是离开之后是否落回合法区间。5. 怎么快速记住这类题记住这句就够了进窗口先加后判出窗口先判后减。十九、学习路线总结真正掌握这两题建议按下面顺序理解第一步先理解固定长度窗口因为异位词/排列一定和目标串长度相同。第二步理解两个计数数组need表示目标需求window表示当前窗口第三步彻底理解count它维护的是已经匹配上的字符总数。第四步记住状态维护顺序这是最关键、也最容易翻车的地方进窗口先加后判出窗口先判后减第五步分清两题唯一差别438记录所有起点567找到一个就返回二十、结语LeetCode 438和LeetCode 567是非常适合放在一起学习的两道题。它们看起来题意不同但本质是同一个滑动窗口模型的两个变体。真正值得掌握的不是死记某一份代码而是下面这套统一思维目标串给出字符需求固定长度窗口在大串中滑动window维护窗口字符统计count维护有效字符总数进窗口先加后判出窗口先判后减只要这套框架真正吃透后面再遇到很多固定窗口类字符串题都会轻松很多。

相关文章:

【码道初阶-Hot100】LeetCode 438 + 567 对照详解:一套滑动窗口模板,彻底讲透“固定长度窗口 + 计数数组 + count维护”

LeetCode 438 567 对照详解:一套滑动窗口模板,彻底讲透“固定长度窗口 计数数组 count维护” 摘要 很多人把 LeetCode 438 和 567 当成两道题分开记,其实完全没必要。它们本质上是同一个固定长度滑动窗口模型,真正难点只在一个…...

大数据隐私保护与数据价值平衡:企业如何做到合规又能用好数据?

大数据隐私保护与价值平衡:企业的“合规用数”实战指南 引言:企业的“数据两难”——锁起来可惜,用起来怕违规 你有没有遇到过这样的困境? 为了符合《个人信息保护法》,把用户数据严严实实地锁在数据库里,看…...

「龙虾」来了!OpenClaw如何掀起AI智能体革命

「龙虾」爆火:OpenClaw的崛起与狂欢 OpenClaw生态系统 #mermaid-svg-CLPHlB6DV7TSkxDt{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{t…...

大模型AI-入门-发展历程-机器学习

部分内容可能来自网络或者由AI生成。 如有雷同,纯属巧合,仅供学习参考之用。机器学习(ML) 机器学习是人工智能的核心分支,其本质是让计算机系统从数据中自动学习规律,并用于预测或决策。一、机器学习的三大…...

【AI Agent 学习笔记 task1】Day2:初识智能体

【AI Agent 学习笔记 task1】Day2:初识智能体 上一篇:【AI Agent 学习笔记】Hello-Agents 环境配置与首个 Agent 实战 一、Agent 的本质 Agent(智能体) 大模型(大脑) 工具(手脚) 控…...

一次生成、无限复用:易元 AI 双引擎重构生产逻辑,AI 混剪素材复用让内容越做越省

内容生产的真正效率,从来不取决于单条视频做得有多快,而在于单次投入能产生多少次价值、一次制作能支撑多少次产出,这就是素材复用的核心价值。在传统模式下普通的混剪工具只是机械拼接、单次产出,无法实现素材沉淀与循环使用&…...

5-11字典合并

输入用字符串表示两个字典,输出合并后的字典。字典的键用一个字母或数字表示。注意:1和‘1’是不同的关键字!输入格式:在第一行中输入第一个字典字符串;在第二行中输入第二个字典字符串。输出格式:在一行中输出合并的字典&#xf…...

86745238

86745238...

AI 模型推理系统的延迟优化方案

AI模型推理系统的延迟优化方案 随着AI技术的广泛应用,模型推理延迟成为影响用户体验和系统性能的关键因素。无论是实时语音识别、自动驾驶,还是在线推荐系统,高延迟都会降低响应速度,甚至导致业务损失。如何优化AI推理系统的延迟…...

LeetCode 3070. 元素和小于等于 k 的子矩阵数目

LeetCode 3070. 元素和小于等于 k 的子矩阵数目 题目描述 给你一个大小为 m x n 的整数矩阵 grid 和一个整数 k。你需要找出 grid 中所有以左上角 (0,0) 为起始点的子矩阵,并统计这些子矩阵中元素和不超过 k 的个数。 注意:子矩阵必须包含 (0,0) 这个格子…...

Java的虚拟线程调度与平台线程池在IO密集型应用中的扩展性

Java虚拟线程与平台线程池在IO密集型应用中的扩展性探索 随着微服务与云原生架构的普及,IO密集型应用对高并发的需求日益增长。传统Java线程模型因平台线程(OS线程)的创建成本高、上下文切换开销大等问题,难以实现高效扩展。Java…...

都跟掉电保护有关,但不是一个东西

以前会误以为 BKP 就等于 RTC因为它们有三个很容易让人混淆的共同点:它们都和“掉电保持”有关它们都在备份域里访问它们时常常都要先打开相关权限于是很容易脑子里变成:既然都和掉电保持有关,那它们是不是一回事其实不是。这就像&#xff1a…...

虚拟实验室:物理化学实验的计算机模拟

虚拟实验室:物理化学实验的计算机模拟 在传统物理化学实验中,学生常受限于设备、安全风险或时间成本,而虚拟实验室通过计算机模拟技术,为学习者提供了全新的实验体验。虚拟实验室不仅能高度还原真实实验场景,还能突破…...

Python的__init_subclass__类方法在框架开发中的钩子机制与扩展点设计

Python作为一门灵活的动态语言,其元编程能力为框架设计提供了强大的扩展性。在众多魔法方法中,__init_subclass__作为Python 3.6引入的类方法,正逐渐成为框架开发中实现钩子机制与扩展点设计的秘密武器。这个特殊方法允许父类在子类创建时进行…...

去中心化应用(DApp)开发全流程

去中心化应用(DApp)开发全流程:从构思到落地 随着区块链技术的普及,去中心化应用(DApp)成为开发者关注的热点。与传统应用不同,DApp运行在区块链网络上,具备透明、不可篡改和去中心…...

Rust Trait 对象动态分派原理

Rust Trait对象动态分派原理探析 Rust作为一门注重安全与性能的系统级语言,其多态实现机制一直是开发者关注的焦点。Trait对象通过动态分派(Dynamic Dispatch)实现了运行时的多态行为,这种机制在需要灵活处理不同类型但共享相同行…...

SSH隧道实战:内网穿透与端口转发

SSH隧道实战:内网穿透与端口转发 在当今数字化时代,远程访问内网资源成为许多企业和开发者的刚需。由于防火墙或NAT的限制,直接访问内网服务往往困难重重。SSH隧道作为一种安全高效的解决方案,能够轻松实现内网穿透和端口转发&am…...

如何设计一个安全的 RESTful API?

如何设计一个安全的 RESTful API?在当今数字化时代,RESTful API 已成为不同系统间数据交互的核心桥梁。随着网络攻击手段的日益复杂,API 的安全性已成为开发者不可忽视的挑战。一个设计不当的 API 可能导致数据泄露、服务瘫痪甚至法律风险。那…...

计算机视觉算法优化

计算机视觉算法优化:让机器更懂世界 计算机视觉作为人工智能的核心领域之一,正深刻改变着我们的生活。从人脸识别到自动驾驶,从医疗影像分析到工业质检,计算机视觉算法的性能直接决定了应用的准确性和效率。随着数据量的爆炸式增…...

STM32:UART串口通信

将一个设备的数据传送到另一个设备时,需要根据情况的不同,制定通信的规则,即通信协议。通信双方按照协议规则进行数据收发。常用的通信协议有名称引脚双工时钟电平设备USARTTX\RX全双工异步单端点对点I2CSCL\SDA半双工同步单端多设备SPISCLK\…...

# WebHID:用 JavaScript 实现浏览器与物理设备的“直连”交互在传统Web 开发中,浏览器对硬件设备的

WebHID:用 JavaScript 实现浏览器与物理设备的“直连”交互 在传统 Web 开发中,浏览器对硬件设备的支持始终受限于安全策略。但随着 WebHID API 的出现,开发者终于可以绕过复杂的驱动层和中间件,直接通过标准 JavaScript 与 USB H…...

Java synchronized 锁优化与偏向锁分析

Java synchronized锁优化与偏向锁分析 在多线程编程中,synchronized关键字是Java实现线程同步的核心机制。早期的synchronized实现因性能问题饱受诟病,直到JVM引入了锁优化技术,尤其是偏向锁的引入,显著提升了并发性能。本文将深…...

Python的__getattr__业务对象

Python魔法方法揭秘:灵活操控属性的__getattr__在Python的面向对象编程中,__getattr__是一个强大而神秘的魔法方法,它像一位隐藏在幕后的属性调度员。当常规属性访问失败时,这个方法就会被自动触发,为开发者提供了处理…...

软件工程软件开发生命周期瀑布模型与敏捷模型的比较

软件工程中的开发模型选择直接影响项目成败,瀑布模型与敏捷模型作为两种经典方法论,分别代表了结构化与灵活性的两极。随着数字化转型加速,开发团队常面临模型选择的困惑。本文将从核心维度对比二者的差异,帮助读者理解不同场景下…...

wythoff构造(正十二面体)

...

C++ 析构函数的隐藏风险

C析构函数的隐藏风险:那些容易被忽视的陷阱 在C编程中,析构函数作为对象生命周期的终结者,负责释放资源、清理内存等重要任务。其看似简单的设计背后却暗藏诸多风险,稍有不慎便可能导致内存泄漏、未定义行为甚至程序崩溃。本文将…...

JavaScript性能优化实战不赜

JavaScript性能优化实战技术文章大纲 性能优化的核心原则 减少代码执行时间 降低内存占用 优化网络请求 提升用户体验 代码层面的优化 避免全局变量污染,使用模块化或闭包 减少DOM操作,批量更新或使用文档片段 使用事件委托减少事件监听器数量 优化循环结…...

C++中的策略模式实战

1、非修改序列算法这些算法不会改变它们所操作的容器中的元素。1.1 find 和 find_iffind(begin, end, value):查找第一个等于 value 的元素,返回迭代器(未找到返回 end)。find_if(begin, end, predicate):查找第一个满…...

开源软件的使用贡献与社区参与经验分享

开源世界的大门:我的贡献与成长之旅 在数字化浪潮中,开源软件已成为技术发展的核心驱动力。从个人开发者到大型企业,无数人通过使用、改进和共享代码推动创新。作为一名长期参与开源项目的技术爱好者,我深刻体会到开源不仅是工具…...

MySQL 查询优化与索引覆盖机制

MySQL查询优化与索引覆盖机制是提升数据库性能的核心技术。随着数据量激增,高效的查询处理成为系统流畅运行的关键。索引覆盖机制通过避免回表操作,显著减少I/O消耗,而查询优化则能从根本上改善执行效率。本文将深入解析其原理与实践方法&…...