题目描述
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
示例 1:
输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。
示例 2:
输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。
思路
- 初始化检查:
- 如果字符串
s的长度小于字符串p,则无法存在异位词,直接返回空结果。
- 如果字符串
- 字符频率映射:
- 使用两个长度为 26 的数组
map1和map2分别记录字符串p和当前滑动窗口内s的字符频率。 charToIndex函数将字符转换为对应的索引('a'对应0,'b'对应1,依此类推)。
- 使用两个长度为 26 的数组
- 初始窗口处理:
- 遍历字符串
p的长度,将p的字符频率存入map1,同时将s中前p.length个字符的频率存入map2。
- 遍历字符串
- 比较初始窗口:
- 定义
isSame函数比较map1和map2是否完全一致。 - 如果初始窗口的字符频率与
p相同,记录起始索引0。
- 定义
- 滑动窗口遍历:
- 从s的第p.length个字符开始,逐步向右滑动窗口:
- 添加新字符:将新进入窗口的字符频率在
map2中增加。 - 移除最左字符:将滑出窗口的字符频率在
map2中减少。 - 比较频率:调用
isSame函数,如果当前窗口的字符频率与p相同,记录当前窗口的起始索引i - p.length + 1。
- 添加新字符:将新进入窗口的字符频率在
- 从s的第p.length个字符开始,逐步向右滑动窗口:
注意点
为什么是i - p.length + 1
- 窗口大小:与字符串
p的长度相同,即p.length。 - 窗口位置:窗口在字符串
s中从左到右滑动,每次移动一个字符。 - 变量
i:表示当前窗口的结束位置(右边界)的索引。
当窗口滑动到位置 i 时,窗口的范围是 [i - p.length + 1, i](包含两端)。因此,窗口的起始位置的索引是 i - p.length + 1。
复杂度分析
- 时间复杂度:
O(n),其中n是字符串s的长度。- 初始化频率映射:遍历
p的长度m,时间复杂度为O(m)。 - 滑动窗口遍历:遍历
s的剩余部分,每一步进行常数时间的操作(添加、移除字符频率和比较),总体为O(n - m)。 - 比较频率:
isSame函数每次比较固定长度的数组(26 个元素),时间复杂度为O(1)。 - 综合:
O(m) + O(n - m) = O(n)。
- 初始化频率映射:遍历
- 空间复杂度:
O(1)。- 使用了两个固定大小的数组
map1和map2,无论输入字符串的大小如何,空间占用保持不变。
- 使用了两个固定大小的数组
code
/**
* @param {string} s
* @param {string} p
* @return {number[]}
*/
var findAnagrams = function (s, p) {
const res = []
if (s.length < p.length) return res
//字母映射
const map1 = Array(26).fill(0), map2 = Array(26).fill(0)
//计算字符在映射中的索引
const charToIndex = (c) => c.charCodeAt() - 'a'.charCodeAt()
//给初始的字符建立映射,后续只需要对s字符串进行遍历修改映射,p是不用变的
for (let i = 0; i < p.length; i++) {
map1[charToIndex(p[i])]++
map2[charToIndex(s[i])]++
}
//比较是否相同
const isSame = () => {
for (let i = 0; i < 26; i++) {
if (map1[i] !== map2[i]) return false
}
return true
}
//如果相同,说明0位置的是异位词
if (isSame()) res.push(0)
//遍历s,重新建立映射(删除左边的,添加正在遍历的)
for (let i = p.length; i < s.length; i++) {
map2[charToIndex(s[i])]++
// 移除最左字符,实际操作的是s[0],s[1],s[2]....位置的这个字母
map2[charToIndex(s[i - p.length])]--
if (isSame()) {
res.push(i - p.length + 1)
}
}
return res
};