题目:
给定两个字符串 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 <= s.length, p.length <= 3 * 104
s 和 p 仅包含小写字母
思路分析:
1.全排列(失败,堆栈溢出)
可以看出s,p的长度都比较大,本来写了一个把p全排列再在s中找子串的算法:
(1)include
/**
* @param {string} s
* @param {string} p
* @return {number[]}
*/
var findAnagrams = function (s, p) {
let len1 = s.length
let len2 = p.length
if(len2>len1){
return []
}
let diff = len1 - len2
let fullPermulateArr = []
let newStr = ""
let numArr = []
fullPermulateArr = fullpermutate(p)
for (let i = 0; i < diff + 1; i++) {
newStr = s.slice(i, i + p.length)
if (fullPermulateArr.includes(newStr)) {
numArr.push(i)
}
}
return numArr
};
function fullpermutate(str) {
var result = [];
if (str.length > 1) {
//遍历每一项
for (var m = 0; m < str.length; m++) {
//拿到当前的元素
var left = str[m];
//除当前元素的其他元素组合
var rest = str.slice(0, m) + str.slice(m + 1, str.length);
//上一次递归返回的全排列
var preResult = fullpermutate(rest);
//组合在一起
for (var i = 0; i < preResult.length; i++) {
var tmp = left + preResult[i]
result.push(tmp);
}
}
} else if (str.length == 1) {
result.push(str);
}
return result;
}
(2)indexOf
/**
* @param {string} s
* @param {string} p
* @return {number[]}
*/
var findAnagrams = function (s, p) {
let len1 = s.length
let len2 = p.length
if (len2 > len1) {
return []
}
let set=new Set()
let diff = len1 - len2
let fullPermulateArr = []
let permulateStr = ""
let index = null
let newIndex = 0
let numArr = []
fullPermulateArr = fullpermutate(p)
for (let j = 0, len = fullPermulateArr.length; j < len; j++) {
permulateStr = fullPermulateArr[j]
newIndex=0
while (s.indexOf(permulateStr, newIndex) !== -1 && newIndex < len1) {
index = s.indexOf(permulateStr, newIndex)
numArr.push(index)
newIndex = index + 1
}
}
set.add(numArr)
return set
};
//求q的全排列
function fullpermutate(str) {
var result = [];
if (str.length > 1) {
//遍历每一项
for (var m = 0; m < str.length; m++) {
//拿到当前的元素
var left = str[m];
//除当前元素的其他元素组合
var rest = str.slice(0, m) + str.slice(m + 1, str.length);
//上一次递归返回的全排列
var preResult = fullpermutate(rest);
//组合在一起
for (var i = 0; i < preResult.length; i++) {
var tmp = left + preResult[i]
result.push(tmp);
}
}
} else if (str.length == 1) {
result.push(str);
}
return result;
}
在p.length=23的时候,n^2的时间复杂度终于顶不住了,这个思路以堆栈溢出而宣告失败…
2.词频统计(桶)
可以new一个大小为26的arr,来统计p所有字母出现的次数.再用滑动窗口对s进行一一匹配(窗口大小固定,为p的长度),每移动一次只需要让s桶的第一个字符的频次-1,指针后移,新添加的元素频次+1,这样不用算全排列;只需要看两个相同大小的桶是否相等就ok了.
/**
* @param {string} s
* @param {string} p
* @return {number[]}
*/
var findAnagrams = function (s, p) {
let len1 = s.length
let len2 = p.length
if(len2>len1){
return []
}
let numArr = []
//使用桶
let Ascii_a = "a".charCodeAt()
let bucket_s = new Array(26).fill(0)
let bucket_p = new Array(26).fill(0)
for (let j = 0; j < len2; j++) {
bucket_p[p[j].charCodeAt() - Ascii_a]++
bucket_s[s[j].charCodeAt() - Ascii_a]++
}
if (bucket_s.toString() === bucket_p.toString()) {
numArr.push(0)
}
for (let i = 0; i < len1 - len2; i++) {
bucket_s[s[i].charCodeAt() - Ascii_a]--
bucket_s[s[i + len2].charCodeAt() - Ascii_a]++
if (bucket_s.toString() === bucket_p.toString()) {
numArr.push(i + 1)
}
}
return numArr
}