LeetCode 438:找到字符串中所有字母异位词——定长窗口 + 欠债种类数
题目描述给定两个字符串s和p找到s中所有p的异位词子串返回这些子串的起始索引。答案顺序任意。异位词字符种类相同每个字符出现次数也相同只是顺序可以不同。例如s cbaebabacdp abc的答案是[0, 6]。最初思路方向是对的用int[128]做频次数组再在s上滑一个长度等于|p|的窗口。第一版额外维护了三个变量cnt当前窗口里已经放了几个字符len还没凑齐的量l左端下标右端i每进来一个字符就a[c]--a[c] 0时len--。然后用两套条件决定要不要从左边弹出窗口已经合法len 0或者窗口已经满长cnt p.length()。问题出在哪里同一轮里两套出窗逻辑可能各执行一次。改成else if之后abab/ab能得到[0, 1, 2]但根因还在。cnt在匹配成功后没有回到「当前窗口的真实长度」。以官方用例为例s cbaebabacd p abc下标0..2的cba是合法窗口。下一轮i 3进来的是e它不在p里窗口已经非法左端却不会继续往右缩e会留在窗口里。另外len一开始按p.length()用个数初始化真正改len时却只在a[c] 0时加减问的是种类刚凑齐或出窗后刚从齐变成缺。个数和种类混在同一个变量里。出窗时还容易把a[s1[left]] 0理解成「左端这个字母是不是p里要找的」。s aabp ab走到窗口aa时左端a确实是要找的字母但当时a[a]并不是0。这个判断真正在问这一类现在是不是刚好凑齐拆掉它之后会不会从「齐」变成「缺」。正确思路先固定一个核心不变量每次判断答案时候选窗口的长度必须恰好等于|p|。右端下标是i时候选窗口的左端为left i - p.length() 1当left 0说明窗口已经达到|p|先判断它是否为异位词再把s[left]移出为下一轮腾位置。因此不需要单独维护l和cnt。频次数组a可以理解为一张「欠债表」a[c]表示窗口还欠p多少个字符c正数窗口里还缺这个字符0数量刚好负数这个字符进多了或者它根本不在p中len只记录「还没凑齐的字符种类数」。初始化时p中每出现一种新字符len。进窗、出窗时只有某个种类在「缺」和「齐」之间切换才修改len。当候选窗口长度等于|p|且len 0时窗口就是异位词。因为所有必需字符都已凑齐而窗口总长度又没有多余位置不可能再混入其他字符。窗口含义每轮要判断的候选窗口是s[left .. i]。在窗口尚未达到|p|时只让字符进窗达到|p|后先判断当前窗口再移出左端字符。扩张与收缩时机扩张右端字符c s1[i]进窗先执行a[c]--。若减完刚好等于0说明这个字符种类从「还缺」变成「刚好」所以len--。收缩当left 0时先判断当前窗口再移出s1[left]。如果移出前a[s1[left]] 0说明这一类原本数量刚好移出后会重新缺一个所以先len再执行a[s1[left]]。这样每轮只进一个字符并在窗口满后只出一个字符。遇到e这种无关字符时它的欠债值会变成负数窗口仍会持续向右滑动直到它被移出。窗口内维护什么只维护欠债表a和欠债种类数len。多进来的字符、不在p里的字符都走负数不会误伤len。原来的cnt没有参与这套不变量可以删掉。手推过程s cbaebabacdp abc。一开始a[a]a[b]a[c]1len 3。i0 进 ca[c]0len2窗口未满 i1 进 ba[b]0len1窗口未满 i2 进 aa[a]0len0left0 记 0出 ca[c] 从 0 变 1len1 i3 进 ea[e]-1len 仍为 1left1 不出答案出 ba[b] 从 0 变 1len2e进来后窗口非法但左端仍会跟着右端移动不会停住。继续滑动到i 8时候选窗口是下标6..8的baclen再次变为0所以记录起点6。最终答案是[0, 6]。再看s aabp ab用来区分「是不是要找的字母」和「这一类是否刚好齐」i0 进 aa[a]0len1窗口未满 i1 进 aa[a]-1len 仍为 1left0 候选窗口是 aa不能记答案 出左端 a此时 a[a]-1不是 0len 不变 再执行 a[a]a[a] 变回 0 i2 进 ba[b]0len0left1 候选窗口是 ab记录起点 1左端那个a虽然属于p但在窗口aa中它是多出来的那个。出窗是否修改len取决于这一类在出窗前是否数量刚好而不是这个字符是否属于p。伪代码下面的伪代码与紧接的 Java 实现逐步对应统计欠债种类、进窗、窗口满后判定、出窗。函数 findAnagrams(s, p): ans - 空列表 a - 长度为 128 的数组初值 0 len - 0 对于 p 中的每个字符 c: 如果 a[c] 0: len - len 1 a[c] - a[c] 1 s1 - s 的字符数组 对于 i 从 0 到 s1.length - 1: a[s1[i]] - a[s1[i]] - 1 如果 a[s1[i]] 0: len - len - 1 left - i - p.length 1 如果 left 0: 如果 len 0: 把 left 加入 ans 如果 a[s1[left]] 0: len - len 1 a[s1[left]] - a[s1[left]] 1 返回 ansJava 代码对应实现只做了排版并去掉不参与逻辑的cntimport java.util.ArrayList; import java.util.List; class Solution { public ListInteger findAnagrams(String s, String p) { ListInteger ans new ArrayList(); int[] a new int[128]; int len 0; for (char c : p.toCharArray()) { if (a[c] 0) len; a[c]; } char[] s1 s.toCharArray(); for (int i 0; i s1.length; i) { a[s1[i]]--; if (a[s1[i]] 0) { len--; } int left i - p.length() 1; if (left 0) { if (len 0) { ans.add(left); } if (a[s1[left]] 0) { len; } a[s1[left]]; } } return ans; } }易错点同一轮不要既按「已经合法」出窗又按「已经满长」再出一次定长写法里进出窗口各一次就够了。遇到不在p中的字符窗口必须继续保持定长往前滑不能只右移一格就停。len要么始终表示还欠的种类要么始终表示还欠的个数不要混用。当前实现是种类进窗后 0才len--出窗前 0才len。a[s1[left]] 0不是在问「它是不是要找的字母」而是在问「这一类现在是否刚好齐」。先判定len 0再出窗。先出窗会把当前合法窗口拆掉答案会漏。没有参与不变量的变量本轮的cnt不要留在最终代码里。建议测试用例s cbaebabacd, p abc 期望[0, 6] 说明中间夹着不在 p 里的 e检查窗口会不会卡住s abab, p ab 期望[0, 1, 2] 说明连续重叠的合法窗口s aab, p ab 期望[1] 说明窗口 aa 中有多余的 a检查出窗时是否错误修改 lens aa, p aa 期望[0] 说明p 里有重复字符s af, p be 期望[] 说明字符种类完全不相交s a, p a 期望[0] 说明最短合法窗口复杂度分析时间复杂度O(|s| |p|)。初始化扫描一次p之后s中的每个字符进窗一次、出窗至多一次。空间复杂度欠债表int[128]是O(1)Java 实现中的toCharArray()会复制s因此整段代码的额外空间为O(|s|)。复盘本轮从「lcnt 两套出窗条件」收成「定长窗口 欠债种类数」。最值得记住的是a[c] 0描述的是种类刚齐或刚缺不是「这个字母在不在p里」非法字符靠负数和定长出窗自然被滑走。正确性可以用cbaebabacd里的e以及aab/ab时多余的a自检。

相关新闻

明富MF-8501包埋柠檬酸:为什么更适合糖果外撒酸粉

明富MF-8501包埋柠檬酸:为什么更适合糖果外撒酸粉

明富MF-8501包埋柠檬酸:为什么更适合糖果外撒酸粉 直接答案 明富MF-8501是一款油脂疏水型包埋柠檬酸,主要面向软糖、硬糖、夹心糖、糖果棒表面外撒酸粉。它要解决的核心问题是:普通柠檬酸易吸潮并可能与体系中其他组分提前接触,造…

2026/9/24 5:14:42 阅读更多 →
抖音【抖币】官方充值脚本逻辑思路

抖音【抖币】官方充值脚本逻辑思路

2026/9/24 5:14:42 阅读更多 →
以太坊出块流程与区块高度:从交易打包到最终确认的完整指南

以太坊出块流程与区块高度:从交易打包到最终确认的完整指南

1. 引言 以太坊作为全球最大的智能合约平台,其核心机制之一就是「出块」。每一笔交易从用户发起,到最终被确认写入区块链,中间经历了一系列复杂而精密的流程。理解以太坊的出块流程,不仅有助于开发者优化 DApp 的交易体验,也能帮助普通用户更好地理解 Gas 费为何波动、交…

2026/9/24 5:14:42 阅读更多 →

最新新闻

STM32F4无感FOC中PLL替代反正切:解决滑膜观测器角度抖动

STM32F4无感FOC中PLL替代反正切:解决滑膜观测器角度抖动

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 6:00:10 阅读更多 →
AIC8800DC驱动移植排错:从SDIO枚举到休眠睡死的完整链路

AIC8800DC驱动移植排错:从SDIO枚举到休眠睡死的完整链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 6:00:10 阅读更多 →
【Python课程设计/毕业设计】基于 Python 爬虫的小说资源阅读平台的设计与实现 综合性小说电子书阅读管理平台的设计与开发【附源码、数据库、万字文档】

【Python课程设计/毕业设计】基于 Python 爬虫的小说资源阅读平台的设计与实现 综合性小说电子书阅读管理平台的设计与开发【附源码、数据库、万字文档】

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/24 6:00:10 阅读更多 →
Python毕业设计-基于 Web 技术的在线小说阅读系统的设计与实现 基于 Python 的轻量化网络小说阅读管理系统的设计与实现(源码+LW+部署文档+全bao+远程调试+代码讲解等)

Python毕业设计-基于 Web 技术的在线小说阅读系统的设计与实现 基于 Python 的轻量化网络小说阅读管理系统的设计与实现(源码+LW+部署文档+全bao+远程调试+代码讲解等)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/24 6:00:10 阅读更多 →
反激电源量产失效的五大工程根源与系统级对策

反激电源量产失效的五大工程根源与系统级对策

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 6:00:10 阅读更多 →
计算机Python毕设实战-基于 Python+Vue 的电子图书阅读管理系统的设计与实现【完整源码+LW+部署说明+演示视频,全bao一条龙等】

计算机Python毕设实战-基于 Python+Vue 的电子图书阅读管理系统的设计与实现【完整源码+LW+部署说明+演示视频,全bao一条龙等】

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/24 5:59:10 阅读更多 →

日新闻

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为…

2026/9/24 0:00:19 阅读更多 →
单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

简介:一份基于单细胞RNA测序数据的细胞类型注释算法研究Python毕业设计源码,针对计算机相关专业正在做毕设或需要项目实战的学习者,可用于课程设计与期末大作业。项目代码完整、经导师指导评审通过,可直接运行,覆盖数据…

2026/9/24 0:00:19 阅读更多 →
C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

第一次在项目里被反射卡住,是在一个老旧的WinForms模块里:几十个类依赖PropertyChanged通知,运行时反射读属性、发通知,每次启动慢半拍不说,一上.NET Native/AOT裁剪模式几乎全面崩盘。后来我把这段逻辑全部改成C#源生…

2026/9/24 0:00:19 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/23 4:55:02 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/23 4:49:06 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/23 9:53:41 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/23 9:53:40 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/23 9:53:40 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/23 9:53:40 阅读更多 →