news 2026/8/17 18:39:31

LeetCode 438:找到字符串中所有字母异位词(滑动窗口) —— 题解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode 438:找到字符串中所有字母异位词(滑动窗口) —— 题解

👋 欢迎阅读

一.题目

438. 找到字符串中所有字母异位词 - 力扣(LeetCode)

🎯 欢迎来到「找到字符串中所有字母异位词」题解之旅!本文将带你从"在长串中滑动窗口寻找异位词"这一直观场景出发,深入理解滑动窗口 + 计数数组的巧妙运用,并掌握如何用有效计数 count 判断命中定位全部异位词起点

在开始之前,建议你先:

  • 了解题目背景:这是 LeetCode 438 题,给定字符串sp,找出s中所有p字母异位词子串起始下标。本质上,异位词即各字母出现次数相同,问题转化为定长窗口内的计数比对

  • 明确学习目标:掌握滑动窗口 + 双计数数组技术,理解有效计数 count的维护原理,并熟练处理窗口超长时的出窗口逻辑

  • 准备好环境:建议在本地 IDE 或 LeetCode 在线编辑器中打开代码,边看边运行,亲手验证示例(如s = "cbaebabacd", p = "abc"输出[0, 6])。

本文将从问题转化、进窗口、出窗口、更新结果到代码实现,层层递进。即使你对滑动窗口还不熟悉,我们也会从"让窗口与 p 等长,边走边核对字母账本"这一直觉出发,让你轻松抓住核心思想——窗口定长滑动,计数对齐即命中。现在,让我们一起滑动窗口,找出所有字母异位词的起点吧! 📍🔍

二.做题思路

一、问题分析(前置分析)

  • 题目要求:在s中找出所有与p互为字母异位词的连续子串,返回其起始下标
  • 关键约束:子串长度必须等于p的长度(定长窗口);仅含小写字母(可用 26 长度计数数组);异位词只看字符频次相等,与顺序无关。
  • 核心思路:维护长度恒为n1的滑动窗口,用有效计数 count把 O(26) 的频次比对压缩为 O(1) 的整数判断。

二、算法策略(滑动窗口 + 计数数组)

核心步骤:

  1. 统计p中各字符频次到hash1[26]
  2. 初始化窗口:left = 0right = 0count = 0hash2[26]记录窗口内频次。
  3. 进窗口right指向的字符加入hash2;若加入后hash2[ch] <= hash1[ch],说明该字符仍在配额内count++
  4. 出窗口:当窗口长度right - left + 1 > n1时,移除left指向的字符:若移除前hash2[ch] <= hash1[ch],说明它曾计入配额,count--,再hash2[ch]--left++
  5. 更新结果:若count == n1,说明窗口内频次与p完全一致,记录left

示例执行过程s = "cbaebabacd"p = "abc",n1 = 3):

步骤变量变化操作结果
预处理hash1[a]=1, b=1, c=1统计 p 的频次计数表就绪
right=0hash2[c]=1, count=1进 'c',配额内未命中
right=1hash2[b]=1, count=2进 'b',配额内未命中
right=2hash2[a]=1, count=3进 'a',配额内,窗口=3命中,记录 left=0
right=3count: 3→2, left=1进 'e' 不计;出 'c' 减 count未命中
right=8count: 2→3, left=6进 'c' 计 1;出 'a' 不减命中,记录 left=6
right=9count: 3→2, left=7进 'd' 不计;出 'b' 减 count未命中

最终返回[0, 6],与题目示例一致。

三、正确性说明(简单版本)

  • 窗口长度恒为 n1:每次右指针前进后,只要长度超过 n1 就立刻收缩左端,保证被判断的窗口始终与 p 等长,不漏检也不重检
  • count 语义可靠:count 表示窗口中"有效字符"总数——即每个字符在不超过 p 需求配额内的累计数量。只有当count == n1时,窗口内频次才恰好全部等于 p 的需求,此时窗口必然是异位词;反之亦然,不会错解
  • 每个起点都被覆盖:left 从 0 一路右移到n2 - n1,所有可能的定长子串恰好被窗口完整覆盖一次,不会漏解
  • 出窗口判定安全:先依据移除前的频次判断是否 count--,再真正减频次,顺序保证 count 始终准确。

四、实现细节(边界防护)

  • 初始化:hash1[26] = {0}hash2[26] = {0}left = 0right = 0count = 0n1 = p.size()n2 = s.size()
  • 边界防护:若n1 > n2,s 中不可能存在长度 n1 的异位词子串,直接返回空;right遍历到n2即停;字符统一用ch - 'a'映射到 0~25。
  • 复杂度:时间 O(n2)(每个字符进、出窗口各一次,count 判断 O(1)),空间 O(1)(两个固定长度 26 的数组)。
  • 关键判断if (hash2[in] <= hash1[in]) count++(进窗口配额判断)、if (right - left + 1 > n1)(窗口收缩判断)、if (count == n1)(命中判断)。

五、返回值(目标映射)

  • 返回v:所有满足条件的子串起始下标。由于 left 从左向右移动,结果自然升序排列,正好对应题目要求的"返回所有起始索引"。

三.代码

class Solution { public: vector<int> findAnagrams(string s, string p) { vector<int> v; // 结果数组:记录所有异位词子串的起始下标 int n1 = p.size(); // p 的长度,即窗口的固定长度 int n2 = s.size(); // 边界防护:s 比 p 短,不可能存在异位词子串,直接返回空 if (n1 > n2) { return v; } int hash1[26] = { 0 }; // p 的频次表:记录 p 中每个字母的出现次数 // 1. 预处理:统计 p 中各字符频次 for (auto ch : p) { hash1[ch - 'a']++; // 'a' 的 ASCII 码是 97,统一映射到 0~25 } int hash2[26] = { 0 }; // 窗口频次表:记录当前窗口内各字符出现次数 // 2. 滑动窗口:进窗口 -> 判断/出窗口 -> 更新结果 for (int left = 0, right = 0, count = 0; right < n2; right++) { // ---------- 进窗口 ---------- int in = s[right] - 'a'; // 进入窗口的字符 hash2[in]++; // 窗口频次 +1 if (hash2[in] <= hash1[in]) { count++; // 该字符仍在 p 的“配额”内,有效计数 +1 } // ---------- 判断 / 出窗口 ---------- if (right - left + 1 > n1) // 窗口长度超出 p 的长度,必须收缩 { int out = s[left] - 'a'; // 即将离开窗口的字符 if (hash2[out] <= hash1[out]) { count--; // 移除前该字符曾计入配额,有效计数 -1 } hash2[out]--; // 窗口频次 -1 left++; // 左指针右移,窗口恢复为 n1 长度 } // ---------- 更新结果 ---------- if (count == n1) // 有效计数等于 p 长度,说明频次完全匹配 { v.push_back(left); // 记录当前窗口起点 } } return v; // 3. 返回所有异位词起始下标 } };

四、易错点分析

难点1:进窗口时"先加频次,再判断配额"的顺序

hash2[in]++; if (hash2[in] <= hash1[in]) { count++; }

count 统计的是窗口中未超出 p 配额的字符个数。进窗口时判断依据必须是加入后的最新频次,所以必须先hash2[in]++再比较;若顺序颠倒,就会用旧频次误判,导致 count 偏小、漏解


难点2:出窗口时 count-- 的判断基于"移除前"的频次

int out = s[left] - 'a'; if (hash2[out] <= hash1[out]) { count--; } hash2[out]--;

这里判断的是"该字符被移除前是否处于配额内"。如果先hash2[out]--再判断,频次已减少,结果可能失真(例如原频次恰好超配额 1,先减后判变成"在配额内",导致 count 漏减)。判断与修改的先后顺序是本题最容易写错的地方


难点3:count 不是"窗口字符总数",而是"有效字符总数"

if (count == n1) { v.push_back(left); }

窗口里可能混入 p 中没有的字符(如 'e'、'd'),它们不计入 count;同一字符超出配额的重复出现也不计入。只有count == n1时,窗口内频次才与 p 完全一致。把 count 误当成窗口长度是常见的理解误区

难点4:窗口收缩条件>>=的一字之差

if (right - left + 1 > n1) { ... left++; }

窗口长度必须恰好等于 n1:短了会漏检,长了会重复。用>保证每轮至多收缩一次,窗口稳定在 n1;若误写成>=,窗口会被压到 n1-1,导致永远无法命中,返回空数组。

五、流程图

🎯 闭幕

🎉 恭喜你完成了「找到字符串中所有字母异位词」问题的学习!

为了巩固知识并进一步拓展,建议你:

🚀动手实践
在 LeetCode 上提交代码,尝试不同的测试用例。

💡深入思考

  • 本题使用固定长度的滑动窗口(窗口大小固定为p.length()),并通过数组哈希表统计窗口内字符频次与p的频次进行比较。请问为什么窗口长度固定为p的长度?如果窗口长度不固定,还能用这种方式吗?

  • 代码中通过count变量记录有效字符数(即窗口内频次不超过p对应频次的字符个数),从而避免每次完整比较两个哈希表。为什么count == n1就能说明窗口是异位词?这背后的等价条件是什么?

  • 当窗口长度超过n1时,代码先判断hash2[out] <= hash1[out]count--,然后再hash2[out]--为什么先判断再减,而不是先减再判断?顺序颠倒会有什么问题?

  • 本题字符集限定为小写英文字母,所以用int hash[26]足够。如果字符串包含Unicode 字符(如中文),应如何改造代码?

  • 滑动窗口的“进窗口 → 判断/出窗口 → 更新结果”三段式结构是本题模板的典型写法。如果将出窗口放在更新结果之后(即先判断再收缩),会有什么影响?

📚延伸挑战

  • 如果题目要求返回所有异位词子串本身(而不是起始下标),你的代码应做哪些调整?

  • 如果p中可能包含重复字符(例如p = "aa"),当前的count计数逻辑是否仍然正确?请结合示例s = "baa"验证。

如果你觉得本文对你有所帮助,欢迎:

👍点赞 / 收藏
👤关注作者,获取更多题解
💬留言交流你的疑问或优化思路


📌深入思考答案

  • 窗口长度固定为n1,因为异位词要求长度相同且字符频次相同,长度不同则不可能匹配,因此固定窗口长度是自然的约束。

  • count == n1等价于完全匹配,因为count记录的是窗口内所有字符的频次都不超过p中对应字符频次的数量,且窗口长度等于n1,这意味着窗口内每个字符的频次恰好等于p中对应字符的频次,即完全匹配。

  • 必须先判断再减,因为判断的是移除前该字符是否在p的配额内,若先减再判断,hash2[out]已变化,判断结果会错误,导致count计数不准。

  • 若字符集为 Unicode,改用unordered_map<char, int>替代固定数组,动态统计频次,适应任意字符。

  • 若先更新结果再收缩,会导致窗口长度大于n1时仍可能被判断为有效,破坏固定窗口的语义,必须先在更新结果前确保窗口长度合法。

🔍延伸挑战答案

  • 挑战1:只需在v.push_back(left)的同时,用s.substr(left, n1)取出子串并存入结果数组即可。

  • 挑战2count逻辑仍然正确。以s="baa", p="aa"为例:初始窗口"ba"countb频次 1 >hash1['b']=0,不计入;a频次 1 ≤hash1['a']=2,计入,count=1,不满足count==2。右移后窗口"aa",两个a均计入,count=2,匹配,逻辑无误。

祝你在算法之路上越走越稳,早日攻克每一道难题!下次见 🚀✨

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/17 18:28:52

零代码造塔指南:用HTML5魔塔样板5分钟做出你的第一款网页游戏

零代码造塔指南&#xff1a;用HTML5魔塔样板5分钟做出你的第一款网页游戏 【免费下载链接】mota-js HTML5魔塔样板&#xff0c;支持全平台游戏&#xff01; 即使完全不会编程的用户&#xff0c;按照模板和说明文档也能很快做出一个魔塔游戏&#xff01; 项目地址: https://gi…

作者头像 李华
网站建设 2026/8/17 18:27:55

合拢这道流

在我的机器上&#xff0c;Claude Code 不会直接跟模型提供商对话。它经过 claude-code-router&#xff08;ccr&#xff09;——一个自托管的代理&#xff0c;让同一个 CLI 既能连官方 Anthropic API&#xff0c;也能连第三方提供商——而我透过 VS Code 插件、隔着 Remote-SSH&…

作者头像 李华