题目:
给定一个字符串 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 <= 104
s 由小写英文字母组成
1 <= words.length <= 5000
1 <= words[i].length <= 30
words[i] 由小写英文字母组成
思路:
由于单词长度相同,利用滑动窗口,每次滑动一个单词的长度,并且计数。由于主串不一定从索引为0开始匹配,窗口初始化的次数也为一个单词的长度。放上代码来理解:
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
//特殊情况直接排除
if (s.empty() || words.empty())return{};
//存放结果的数组
vector<int >result;
//一个单词的长度、words中的单词数量、总长度
int one_word = words[0].size();
int word_num = words.size();
int all_len = one_word * word_num;
//建立单词->单词个数得映射
unordered_map<string, int>m1;
for (const auto& w : words)m1[w]++;
//窗口初始化的次数为one_word
for (int i = 0; i < one_word; i++) {
//窗口的左右边界,匹配得单词总数
int left = i, right = i, count = 0;
unordered_map<string, int>m2;
//开始滑动窗口
while (right + one_word <= s.size()) {
//s中提取一个单词到w
string w = s.substr(right, one_word);
right += one_word;//右边界右移
//单词不在m1中匹配不成功,重置边界、单词个数、m2
if (m1.count(w) == 0) {
count = 0;
left = right;
m2.clear();
}
//匹配成功,添加到m2
else {
m2[w]++;
count++;
//匹配次数多了,左边界left右移
while (m2.at(w) > m1.at(w)) {
string t_w = s.substr(left, one_word);
count--;
m2[t_w]--;
left += one_word;
}
if (count == word_num)result.push_back(left);
}
}
}
return result;
}
};