给定一个字符串 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”]
输出:[]
解析:对于字符串的相关问题,许多时候可以用滑动窗口的方式解决,即先扩大范围即右边界,直到不符合要求时,再推进左边界缩小范围。
具体解析见代码
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
unordered_map<string,int> word_map;
vector<int> res;
if(words.size()==0) return res;
int word_len = words[0].size();
int word_num = words.size();
if(s.size()<word_num*word_len) return res;
//构造目标map
for(string str:words) word_map[str]++;
//遍历字符串word_len次即可覆盖所有可能情况
for(int i=0;i<word_len;++i){
//子串的左右边界
int left = i;
int right = i;
//子串查找map
unordered_map<string, int> find_map;
//成功匹配的个数
int count = 0;
//如果右边界能够继续扩展,则扩展右边界
//此处右边界在最后匹配位置的右侧,因此是小于等于。
while(right+word_len<=s.size()){
//获取待匹配的子串
string cur = s.substr(right, word_len);
//匹配个数加一
++count;
//右边界继续推进
right += word_len;
//如果该子串不存于目标map中,即该单词不存在与words中
//代表包括该子串在内的目前边界,必然无法与words匹配,
//因此放弃已经匹配的结果,将左边界推进至等于右边界
if(word_map[cur]==0){
count=0;
left=right;
find_map.clear();
continue;
}
//否则该单词存在于words中,对应查找map值加一
find_map[cur]++;
//此时可能存在一种情况,即该单词出现次数过多,超过了words中的数量
//则将左边界向右推进,将因推进而离开子串边界的单词从查找map中去除
//直至该单词的数量符合要求,注意推进会减少已经匹配单词的数量。
while(find_map[cur]>word_map[cur]){
string lefts = s.substr(left, word_len);
left += word_len;
find_map[lefts]--;
count--;
}
//处理后,如果匹配单词的数量达到了words_len
//即目前的子串与words完全匹配,将其左边界即子串起始位置索引返回
if(count==word_num) res.push_back(left);
}
}
return res;
}
};