题目描述
给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。(word中单词只出现一次)
示例 1:
输入:
s = “barfoothefoobarman”,
words = [“foo”,“bar”]
输出:[0,9]
解释:从索引 0 和 9 开始的子串分别是 “barfoo” 和 “foobar” 。输出的顺序不重要, [9,0] 也是有效答案。
示例 2:
输入:
s = “wordgoodgoodgoodbestword”,
words = [“word”,“good”,“best”,“word”]
输出:[]
题解
滑动窗口
- 因为单词的长度 len 是固定的,可将一个单词看成一个单元
- 对单词使用滑动窗口,每次步长就是 len
- 在 0 - len 的范围内,每一个都作为滑动窗口的起点,滑动 len 次,即可覆盖所有字符串的各种组合
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
if(words.size() == 0) return {};
unordered_map<string, int> wordcount;
for(auto &w:words) wordcount[w]++;//统计words中每个单词次数
int len = words[0].size();//words中每个单词等长,计算其单词长度
vector<int> ans;
for(int i = 0; i < len; i++){//在 0 - len 的范围内,每一个都作为滑动窗口的起点,滑动 len 次,即可覆盖所有字符串的各种组合
int left = i;
int right = left;
int count = 0;
unordered_map<string, int> window;
while(left + words.size()*len <= s.size()){
string temp = "";
while(count < words.size()){//判断当前滑动窗口window中元素是否由words中单词组成
temp = s.substr(right, len);//子串是在字符位置right开始,跨越len个字符
if(wordcount.find(temp) == wordcount.end() || window[temp] >= wordcount[temp]) break;//wordcount中没有子串temp,或者window中子串temp的数量大于wors中相应单词数量,则退出while循环,说明当前window中元素不满足条件
window[temp]++;//计算当前window中单词temp次数
count++;
right += len;//在当前window中以单个单词长度遍历
}
if(window == wordcount) ans.push_back(left);//如果window中单词及其出现次数与words一样,记录遍历起始位置
//此时temp字串在words中,window遍历时向右移动words中单个单词长度,进行下一轮遍历
if(wordcount.find(temp) != wordcount.end()){
window[s.substr(left, len)]--;
count--;
left += len;
}
else{//此时temp字串不在words中,window遍历时会跳过当前子串,以words中单个单词长度向右移动,进行下一轮遍历,比如图示中的xba子串
right += len;
left = right;
count = 0;
window.clear();
}
}
}
return ans;
}
};
复杂度分析
- 时间复杂度: O(n2)
- 空间复杂度: O(n)
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> result;
if(s.empty() || words.empty()) return result;
// 单词数组中的单词的大小,个数以及总长度
int word_len=words[0].size(), word_num=words.size();
int all_len=word_len*word_num;
unordered_map<string, int> map; //建立单词->单词个数的映射
for(const auto& w : words)
map[w]++;
for(int i=0; i<word_len; ++i) {
// left和rigth表示窗口的左右边界,count用来统计匹配的单词个数
int left=i, right=i, count=0;
unordered_map<string, int> tmp;
// 开始滑动窗口
while(right+word_len <= s.size()) {
// 从s中提取一个单词拷贝到w
string w=s.substr(right, word_len);
right += word_len; // 窗口右边界右移一个单词的长度
if(map.count(w)==0) { // 此单词不在words中
count=0;
left=right;
tmp.clear();
}
else { // 该单词匹配成功,添加到tmp中
tmp[w]++;
count++;
while(tmp.at(w) > map.at(w)) { // 一个单词匹配多次
string t_w=s.substr(left,word_len);
count--;
tmp[t_w]--;
left+=word_len;
}
if(count==word_num) result.push_back(left);
}
}
}
return result;
}
};
参考
【串联所有单词的子串】循环 len 次滑动窗口
python三种方法逐渐优化 击败99.9%
C++ 滑动窗口 + 字符串哈希
串联所有单词的子串