持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第23天,点击查看活动详情
给定一个字符串 s ****和一些 长度相同 的单词 words **。**找出 s ****中恰好可以由 words ****中所有单词串联形成的子串的起始位置。
注意子串要与 words ****中的单词完全匹配,中间不能有其他字符 ,但不需要考虑 words ****中单词串联的顺序。
示例 1:
输入:s = "barfoothefoobarman", words = ["foo","bar"]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。
输出的顺序不重要, [9,0] 也是有效答案。
示例 2:
输入:s= "wordgoodgoodgoodbestword", words = ["word","good","best","word"]
输出:[]
示例 3:
输入:s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]
输出:[6,9,12]
滑动窗口
记 的长度为 , 中每个单词的长度为 , 的长度为 。首先需要将 划分为单词组,每个单词的大小均为 (首尾除外)。这样的划分方法有 种,即先删去前 ()个字母后,将剩下的字母进行划分,如果末尾有不到 个字母也删去。对这 种划分得到的单词数组分别使用滑动窗口对 进行类似于「字母异位词」的搜寻。
划分成单词组后,一个窗口包含 中前 个单词,用一个哈希表 表示窗口中单词频次和 中单词频次之差。初始化 时,出现在窗口中的单词,每出现一次,相应的值增加 ,出现在 中的单词,每出现一次,相应的值减少 。然后将窗口右移,右侧会加入一个单词,左侧会移出一个单词,并对 做相应的更新。窗口移动时,若出现 中值不为 的键的数量为 ,则表示这个窗口中的单词频次和 中单词频次相同,窗口的左端点是一个待求的起始位置。划分的方法有 种,做 次滑动窗口后,即可找到所有的起始位置。
var findSubstring = function(s, words) {
const res = [];
const m = words.length, n = words[0].length, ls = s.length;
for (let i = 0; i < n; i++) {
if (i + m * n > ls) {
break;
}
const differ = new Map();
for (let j = 0; j < m; j++) {
const word = s.substring(i + j * n, i + (j + 1) * n);
differ.set(word, (differ.get(word) || 0) + 1);
}
for (const word of words) {
differ.set(word, (differ.get(word) || 0) - 1);
if (differ.get(word) === 0) {
differ.delete(word);
}
}
for (let start = i; start < ls - m * n + 1; start += n) {
if (start !== i) {
let word = s.substring(start + (m - 1) * n, start + m * n);
differ.set(word, (differ.get(word) || 0) + 1);
if (differ.get(word) === 0) {
differ.delete(word);
}
word = s.substring(start - n, start);
differ.set(word, (differ.get(word) || 0) - 1);
if (differ.get(word) === 0) {
differ.delete(word);
}
}
if (differ.size === 0) {
res.push(start);
}
}
}
return res;
};