这个解题思路的核心是利用固定大小的滑动窗口和数组统计字符频率来高效判断子串是否为异位词。以下是详细解释:
1. 核心思路
- 异位词的定义:两个字符串包含的字符及其数量完全相同,只是顺序可能不同。例如,“abc” 和 “bca” 是异位词。
- 滑动窗口的适用性:由于异位词的长度必须与原字符串相同,因此可以在
s
中滑动一个长度为p.size()
的窗口,逐一检查窗口内的字符频率是否与p
一致。
2. 算法步骤
步骤1:预处理字符频率数组
- 创建两个长度为 26 的数组(假设只包含小写字母):
target[26]
:统计p
中每个字符的出现次数。window[26]
:统计当前窗口中每个字符的出现次数。
步骤2:初始化第一个窗口
- 遍历
s
的前p.size()
个字符,将字符频率填入window
数组。
步骤3:滑动窗口并检查匹配
- 固定窗口大小:窗口始终保持长度为
p.size()
。 - 每次滑动:
- 右移窗口:将右侧新字符加入窗口(更新
window
数组)。 - 移出左侧字符:将左侧旧字符移出窗口(更新
window
数组)。 - 检查匹配:比较
window
和target
是否完全相同。若相同,则记录当前窗口的起始位置。
- 右移窗口:将右侧新字符加入窗口(更新
3. 关键优化点
用数组替代哈希表
- 由于字符集较小(26个小写字母),使用数组比哈希表更高效。数组的随机访问时间复杂度为 O(1),且无需处理哈希冲突。
固定窗口大小
- 窗口大小始终等于
p.size()
,避免了动态调整窗口的复杂性,简化了逻辑。
快速比较数组
- 直接比较两个数组(
window == target
)的时间复杂度为 O(26) = O(1),因为数组长度固定。
4. 处理特殊情况
- 当
s.size() < p.size()
时:直接返回空结果,因为无法构造有效窗口。
5. 复杂度分析
- 时间复杂度:O(n),其中 n 是
s
的长度。每个字符仅被访问两次(加入窗口和移出窗口)。 - 空间复杂度:O(1),因为数组大小固定为 26,不随输入长度变化。
6. 示例代码(参考力扣官方题解)
vector<int> findAnagrams(string s, string p) {vector<int> result;if (s.size() < p.size()) return result;vector<int> target(26, 0);vector<int> window(26, 0);// 统计p中字符频率for (char c : p) target[c - 'a']++;// 初始化第一个窗口for (int i = 0; i < p.size(); i++) {window[s[i] - 'a']++;}// 检查第一个窗口if (window == target) result.push_back(0);// 滑动窗口for (int i = p.size(); i < s.size(); i++) {// 移出左侧字符window[s[i - p.size()] - 'a']--;// 移入右侧字符window[s[i] - 'a']++;// 检查当前窗口if (window == target) result.push_back(i - p.size() + 1);}return result;
}
7. 与你的测试用例的关系
在你的测试用例中,s
和 p
都很长,但主要由 a
组成。上述算法通过固定窗口大小和数组统计,能高效处理这种情况,避免了频繁哈希表操作的开销。每次滑动窗口只需 O(1) 时间更新频率并比较数组,因此即使字符串很长,也能快速找到所有异位词位置。