【LeetCode-30】串联所有单词的子串
问题
给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。
示例
输入:
s = "barfoothefoobarman",
words = ["foo","bar"]
输出: [0,9]
解释:
从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。
输出的顺序不重要, [9,0] 也是有效答案。
解答1:定长滑动窗口+哈希表
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
int n = words.size(), w = words[0].size();
int end_pos = s.size() - n * w;
vector<int> res;
unordered_map<string, int> w_memo;
for (string& i : words) w_memo[i]++; // 目标哈希表初始化
for (int i = 0; i <= end_pos; i++) {
unordered_map<string, int> s_memo;
for (int j = 0; j < n; j++) {
string cur = s.substr(i + j * w, w);
if (!w_memo.count(cur)) break; // 剪枝
s_memo[cur]++;
}
if (s_memo == w_memo) res.push_back(i);
}
return res;
}
};
重点思路
可以先看本题的简化版【LeetCode-567】字符串的排列。本题需要做必要的剪枝。
解答2:变长滑动窗口+哈希表
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> res;
int n = words.size(), w = words[0].size();
unordered_map<string, int> w_memo;
for (string& i : words) w_memo[i]++;
for (int i = 0; i < w; i++) { // 共有w种滑动窗口的情况
unordered_map<string, int> s_memo;
int left = i, right = i;
while (right + w <= s.size()) { // 开始滑动窗口
string cur = s.substr(right, w);
right += w;
if (w_memo.count(cur)) { // 如果要加入的这个存在
s_memo[cur]++;
while (w_memo[cur] < s_memo[cur]) {
s_memo[s.substr(left, w)]--;
left += w;
}
if (right - left == n * w) res.push_back(left); // 长度是否满足要求
} else { // 否则直接跳过这个
left = right;
s_memo.clear();
}
}
}
return res;
}
};
重点思路
本题如果要使用变长的滑动窗口,首先需要讨论有几种滑动窗口的情况。由于字典中的字符长度都是相同的,我们可以使用类似于【LeetCode-567】字符串的排列这道题的做法,将“单词”当作那道题的“字符”