一、题目理解:什么是 "异位词子串"?
简单说:字符串s中,长度和p相等、且字符出现次数完全一致的子串,就是我们要找的 "异位词子串",最终返回这些子串的起始索引。
比如示例 1 里,p=abc(长度 3),s=cbaebacbd中cba(索引 0)、bac(索引 6)都是abc的异位词。
二、核心思路:滑动窗口 + 字符频率数组
异位词的本质是 "字符频率完全匹配",所以我们可以用一个长度为 26 的数组(对应 26 个小写字母) 统计p的字符频率,再用滑动窗口 在s中动态维护当前窗口的字符频率,一旦窗口长度等于p的长度,就说明找到了一个异位词子串。
三、代码逐行解析(C++ 版)
直接看代码 + 注释,逻辑超清晰:
cpp
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int> v; // 存储结果(异位词子串的起始索引)
int ch[26] = {0}; // 统计p的字符频率(初始全0)
// 1. 先统计p中每个字符的出现次数
for(int i=0; i<p.size(); ++i)
ch[p[i]-'a']++; // 比如p[i]是'a',则ch[0]++
int left=0; // 滑动窗口的左边界
// 2. 右边界right遍历s的每个字符
for(int right=0; right<s.size(); ++right)
{
int c = s[right]-'a'; // 当前字符对应的数组下标
ch[c]--; // 窗口右移,当前字符进入窗口,频率-1
// 3. 若当前字符频率<0,说明窗口里该字符多了,左边界右移"挤掉"多余字符
while(ch[c]<0)
{
++ch[s[left]-'a']; // 左边界字符移出窗口,频率+1
++left; // 左边界右移
}
// 4. 当窗口长度等于p的长度时,说明找到异位词
if(right-left+1 == p.size())
v.push_back(left); // 记录当前窗口的起始索引left
}
return v;
}
};
四、为什么这个思路高效?
滑动窗口的时间复杂度是O(n) (n 是s的长度),因为left和right都只遍历一次;字符频率数组的空间复杂度是O(1)(固定 26 个元素),属于 "时间换空间" 的最优解之一。
五、实战小技巧
遇到 "子串匹配 + 字符频率" 类题目,优先考虑滑动窗口 + 固定长度数组的组合:
- 用数组统计目标串的频率
- 窗口在原串中动态维护频率
- 窗口长度匹配时直接记录结果