1007 字
5 分钟
找到字符串中所有字母异位词

题目描述#

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

解题思路#

  1. 滑动窗口法
    • 初始化两个频率数组(长度26)存储字符计数
    • 先记录模式p的字符频率
    • 初始化窗口为s的前p长度个字符
  2. 窗口滑动
    • 每次右移窗口:左边界字符计数减1,右边界字符计数加1
    • 比较当前窗口频率与模式频率
    • 若匹配则记录起始索引
  3. 频率匹配
    • 使用常数时间比较频率数组
    • 避免重新扫描整个窗口
  4. 边界处理
    • 处理空字符串和短于p的s
    • 字符索引映射:charCodeAt() - 97

关键洞察#

  1. 异位词本质
    • 字符组成完全相同(频率一致)
    • 与顺序无关
  2. 频率数组优势
    • 替代昂贵的子串排序操作
    • 常数时间完成频率比较
  3. 滑动窗口效率
    • 窗口平移时只更新两个字符的计数
    • 避免O(n×m)的暴力扫描
  4. 字符索引优化
    • 字母映射到0-25的整数索引
    • 直接访问数组元素

复杂度分析#

指标说明
时间复杂度O(n):n为s长度,每个字符处理两次(进/出)
空间复杂度O(1):固定大小频率数组(26×2)

代码实现#

/**
* @link {https://leetcode.cn/problems/find-all-anagrams-in-a-string/}
* @param {string} s
* @param {string} p
* @return {number[]}
*/
const findAnagrams = (s, p) => {
if (s.length < p.length) return [];
const pLen = p.length;
const sLen = s.length;
const result = [];
// 初始化频率数组(26个字母)
const pCount = new Array(26).fill(0);
const winCount = new Array(26).fill(0);
// 记录模式p的频率
for (let i = 0; i < pLen; i++) {
pCount[p.charCodeAt(i) - 97]++;
}
// 初始化窗口频率
for (let i = 0; i < pLen; i++) {
winCount[s.charCodeAt(i) - 97]++;
}
// 比较初始窗口
if (arrayEqual(pCount, winCount)) {
result.push(0);
}
// 滑动窗口遍历
for (let i = pLen; i < sLen; i++) {
// 移除左边界字符
winCount[s.charCodeAt(i - pLen) - 97]--;
// 添加右边界字符
winCount[s.charCodeAt(i) - 97]++;
// 比较频率
if (arrayEqual(pCount, winCount)) {
result.push(i - pLen + 1);
}
}
return result;
};
// 常数时间频率数组比较
const arrayEqual = (arr1, arr2) => {
for (let i = 0; i < 26; i++) {
if (arr1[i] !== arr2[i]) return false;
}
return true;
};
/**
* @param {string} s
* @param {string} p
* @return {number[]}
*/
var findAnagrams = function (s, p) {
const sLen = s.length;
const pLen = p.length;
if (sLen < pLen) return [];
const result = [];
const count = new Array(26).fill(0);
const aCharCode = 'a'.charCodeAt(); // 预计算 'a' 的编码
// 初始化窗口:统计s前pLen个字符和p的字符差异
for (let i = 0; i < pLen; i++) {
count[s.charCodeAt(i) - aCharCode]++;
count[p.charCodeAt(i) - aCharCode]--;
}
// 计算初始差异数
let differ = 0;
for (let i = 0; i < 26; i++) {
if (count[i] !== 0) differ++;
}
if (differ === 0) result.push(0);
// 滑动窗口:每次移动一位
for (let i = 0; i < sLen - pLen; i++) {
const leftChar = s.charCodeAt(i) - aCharCode;
const rightChar = s.charCodeAt(i + pLen) - aCharCode;
// 处理左边界移出的字符
if (count[leftChar] === 1) {
differ--; // 从1变0,差异减少
} else if (count[leftChar] === 0) {
differ++; // 从0变-1,差异增加
}
count[leftChar]--;
// 处理右边界移入的字符
if (count[rightChar] === -1) {
differ--; // 从-1变0,差异减少
} else if (count[rightChar] === 0) {
differ++; // 从0变1,差异增加
}
count[rightChar]++;
// 差异为0表示找到异位词
if (differ === 0) result.push(i + 1);
}
return result;
};

算法执行流程#

输入: s="cbaebabacd", p="abc"
步骤:
1. p频率: [a:1,b:1,c:1] → [1,1,1,0,...]
2. 初始窗口s[0:2]="cba" → [1,1,1,0,...] → 匹配 → 记录0
3. 窗口右移:
   - 移除s[0]='c' → [1,1,0,...]
   - 添加s[3]='e' → [1,1,0,1,...] → 不匹配
4. 继续右移:
   - ...直到窗口"bae"→"bab"→"bac"
   - s[6:8]="bac" → [1,1,1,0,...] → 匹配 → 记录6

实际应用场景#

  1. 生物信息学
// DNA序列中查找相似基因片段
// s="ATCGAATTCG", p="AATT" → 位置[4]`
  1. 文本搜索
// 文档中查找变位词(如"listen"和"silent")`
  1. 密码分析
// 检测密文中的预设模式

相似题目#

找到字符串中所有字母异位词
https://website-truelovings-projects.vercel.app/posts/algorithms/找到字符串中所有字母异位词/
作者
欢迎来到StarSky的网站!
发布于
2025-08-17
许可协议
CC BY-NC-SA 4.0