开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情
给定一个字符串 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]
提示:
1 <= s.length <= 10^4s由小写英文字母组成1 <= words.length <= 50001 <= words[i].length <= 30words[i]由小写英文字母组成
思路
本题可以用滑动窗口求解。设words的长度为m,words中每个单词的长度为n。首先将s划分为单词组,每个单词长度为n(首尾单词除外),共有n种划分方法。划分好单词组后就可以用滑动窗口求解了。
一个窗口中包含s中的m个单词,用一个哈希表map来存储窗口中单词出现次数和words中单词次数的差。初始化时,遍历words中每个单词,出现一次map中word减1,然后遍历窗口中每一个单词,出现一次map中对应的word加1,当map中所有word的值都为0时,就是恰好匹配words中的所有单词了。滑动窗口时,划出的word减1,划入的加1。
解题
/**
* @param {string} s
* @param {string[]} words
* @return {number[]}
*/
var findSubstring = function (s, words) {
const res = [];
const m = words.length;
const n = words[0].length;
const total = m * n;
const len = s.length;
if (total > len) return res;
const split = (s, start) => {
const arr = [""];
let end = start + n;
while (end <= len) {
arr.push(s.substring(start, end));
start = end;
end += n;
}
return arr;
};
const counter = (map, word, count) => {
count += map.get(word) || 0;
if (count === 0) {
map.delete(word);
} else {
map.set(word, count);
}
};
for (let i = 0; i < n; i++) {
if (i + total > len) break;
const map = new Map();
let arrs = split(s, i);
for (let j = 0; j < m; j++) {
counter(map, words[j], -1);
}
for (let j = 0; j < m; j++) {
counter(map, arrs[j], 1);
}
for (let j = m; j < arrs.length; j++) {
counter(map, arrs[j - m], -1);
counter(map, arrs[j], 1);
if (map.size === 0) {
res.push(i + (j - m) * n);
}
}
}
return res;
};