给定两个字符串
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" 的异位词。
题解1 暴力解法
思路
其实和上一题类似,就是遍历每个字母为起始位,然后向后遍历 p 个字符,与 p 做计数比较,相等则加入到集合里。
暴力解法会超时,但还是把代码贴一下。
代码
function findAnagrams(s: string, p: string): number[] {
const isSame = (str1: string, str2: string): boolean => {
if (str1.length !== str2.length) return false;
const count = {};
for (let char of str1) {
count[char] = (count[char] || 0) + 1;
}
for (let char of str2) {
if (!count[char]) {
return false;
} else {
count[char]--;
}
}
return true;
}
const result: number[] = [];
for (let i = 0; i <= s.length - p.length; i++) {
if (isSame(s.slice(i, i + p.length), p)) {
result.push(i);
}
}
return result;
};
时空复杂度分析
时间复杂度:s的长度为n, p的长度为m,时间复杂度为 O(nm)
空间复杂度:isSame的空间复杂度是常数,因为最多只有26个字母,而slice会创建一个新的字符串,所以空间复杂度为O(m)
题解2 滑动窗口
思路
暴力的优化点和上一题类似,要优化重复计算。
在需要经常比较两个字符串是否相等,可以用两个数组统计 ascii 码的次数,然后使用 toString 方法来比较相等。
right 指针一直往后遍历到结尾,将每个字符的的ascii 码加入到数组中,如果当前窗口大于 p 的长度,那么将 left 指针收紧。
代码
function findAnagrams(s: string, p: string): number[] {
const pCount: number[] = Array(26).fill(0);
const sCount: number[] = Array(26).fill(0);
const aCharCode: number = 'a'.charCodeAt(0);
for (let i = 0; i < p.length; i++) {
pCount[p.charCodeAt(i) - aCharCode]++; // 统计 p 中每个字符的频率
}
const result: number[] = [];
let left: number = 0;
let right: number = 0;
while (right < s.length) {
// 扩展窗口,添加右边的字符
sCount[s.charCodeAt(right) - aCharCode]++;
// 当窗口大小超过 p 的长度时,左边收缩窗口
if (right - left + 1 > p.length) {
sCount[s.charCodeAt(left) - aCharCode]--;
left++;
}
// 判断当前窗口是否是异位词
if (pCount.toString() === sCount.toString()) {
result.push(left);
}
right++;
}
return result;
};
时空复杂度分析
时间复杂度:只遍历了一次字符串,所以是 O(n)
空间复杂度:使用了常量数组,复杂度为 O(1)