这道题也是非常经典的滑动窗口,一般的思路就是创建一个unordered_map<string,int>wordcount用于存储words中每个单词出现的次数,然后创建一个滑动窗口,之后遍历字符串,如果当前窗口内的单词都在words中且次数出现正确,则把索引加入结果,以下是代码:
// 计算每个单词的长度
int wordLen = words[0].length();
// 计算所有单词的总长度
int totalLen = wordLen * words.size();
// 如果字符串长度小于所有单词的总长度,直接返回空
if (s.length() < totalLen) {
return {};
}
// 创建一个哈希表,存储 words 中每个单词出现的次数
unordered_map<string, int> wordCount;
for (const auto& word : words) {
wordCount[word]++;
}
vector<int> result;
// 遍历字符串 s,维护一个滑动窗口
for (int i = 0; i <= s.length() - totalLen; i++) {
// 复制一份 wordCount,用于当前窗口
unordered_map<string, int> currentCount(wordCount);
int j = 0;
// 检查当前窗口中的单词是否都在 words 中,且出现次数正确
for (; j < words.size(); j++) {
string word = s.substr(i + j * wordLen, wordLen);
if (currentCount.count(word) == 0 || currentCount[word] == 0) {
break;
}
currentCount[word]--;
}
// 如果所有单词都匹配,则将起始索引加入结果
if (j == words.size()) {
result.push_back(i);
}
}
return result;
}
这种情况在数据量小的情况下可以胜任,但是一旦数据量多了,就会超时,那我么怎么解决呢
其实我们要做的就是解决一下不必要的窗口操作
比如一旦出现一个不是words中的单词,则此窗口不符合,直接进行下一个窗口,解决不必要的操作
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
if (s.empty() || words.empty()) {
return {};
}
// 计算每个单词的长度
int wordLen = words[0].length();
// 计算所有单词的总长度
int totalLen = wordLen * words.size();
// 如果字符串长度小于所有单词的总长度,直接返回空
if (s.length() < totalLen) {
return {};
}
// 创建一个哈希表,存储 words 中每个单词出现的次数
unordered_map<string, int> wordCount;
for (const auto& word : words) {
wordCount[word]++;
}
vector<int> result;
// 遍历每一个可能的起始点
for (int i = 0; i < wordLen; i++) {
unordered_map<string, int> currentCount;
int left = i, count = 0;
for (int j = i; j <= s.length() - wordLen; j += wordLen) {
string word = s.substr(j, wordLen);
if (wordCount.count(word)) {
currentCount[word]++;
count++;
while (currentCount[word] > wordCount[word]) {
string leftWord = s.substr(left, wordLen);
currentCount[leftWord]--;
count--;
left += wordLen;
}
if (count == words.size()) {
result.push_back(left);
string leftWord = s.substr(left, wordLen);
currentCount[leftWord]--;
count--;
left += wordLen;
}
} else {
currentCount.clear();
count = 0;
left = j + wordLen;
}
}
}
return result;
}
};
优化解释
-
多起始点滑动窗口:
- 我们遍历每一个可能的起始点
i
,从0
到wordLen-1
。这样可以保证所有可能的起始位置都被考虑到。
- 我们遍历每一个可能的起始点
-
滑动窗口调整:
- 在滑动窗口内,我们通过右移窗口并增加
currentCount
来记录单词的出现次数。 - 如果当前单词出现次数超过
wordCount
中记录的次数,我们通过左移窗口来调整窗口大小,直到窗口内的单词出现次数符合要求。 - 当窗口内单词的总数等于
words.size()
时,表示我们找到一个有效的起始位置。
- 在滑动窗口内,我们通过右移窗口并增加
-
提前退出:
- 如果遇到一个不在
words
中的单词,我们立即清空currentCount
并重置窗口,避免不必要的计算
- 如果遇到一个不在