30. 串联所有单词的子串
题目
给定一个字符串 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 <= 10^4
- s 由小写英文字母组成
- 1 <= words.length <= 5000
- 1 <= words[i].length <= 30
- words[i] 由小写英文字母组成
暴力法
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> ret;
if(s.length()==0 || words.size()==0)
return ret;
unordered_map<string,int>m;
for(auto word:words)//映射的初始化
{
if(m.find(word) == m.end())
m[word]=1;
else
m[word]++;
}
int n = s.length();
int size = words.size();
int len = words[0].size();
for(int i = 0; i<=n-size*len; i++)//遍历所有可能的起点进行判断
{
unordered_map<string,int> temp;
int j = i;
for(; j<i+size*len; j+=len)
{
string word = s.substr(j, len);
if(m.find(word) != m.end())
{
if(temp.find(word) == temp.end())
temp[word]=1;
else
temp[word]++;
if(temp[word] > m[word])//如果此单词出现次数超出,则i位置不合法
break;
}
else break;
}
if(j==i+size*len)
ret.push_back(i);
}
return ret;
}
};
滑动窗口法
- 我们可以将起点根据当前下标与单词长度的取余结果进行分类,然后进行下面的滑动窗口(每次移动一个单词)操作,这样,我们枚举了所有起点分类的可能情况,这样可以做到不重不漏
- 在起点的所属某一个类别的情况下,由于words中所有单词的长度是相同的,所以,我们滑动一个单词,下一个起点位置还是属于这个类别,并且我们可以复用上一个阶段的窗口内的单词的计数统计信息,不用再从头开始计数,我们通过不断的滑动,可以遍历所有以属于这个类别的起点的情况,外循环又枚举了所有的起点可以属于的类,这样就可以做到不重不漏
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> ret = new ArrayList<>();
int n = s.length();
int m = words.length;
int w_len = words[0].length();
if(n == 0 || w_len == 0)
return ret;
Map<String, Integer> wordsCount = new HashMap<>();
for(String word : words)
{
// 将所有单词加入 HashMap,并计数
wordsCount.put(word, wordsCount.getOrDefault(word, 0) + 1);
}
for(int i = 0; i < w_len; ++i)
{
// 错位循环,保证每种情况都遍历到
Map<String, Integer> window = new HashMap<>();
int left = i;
int right = i;
while(right+w_len <= n && left+w_len*m <= n)
{
String subRight = s.substring(right, right + w_len);
// 如果这个单词不在 words 中,就重置窗口
if(!wordsCount.containsKey(subRight))
{
window.clear();
right += w_len;
left = right;
continue;
}
else
{
// 将刚进入窗口并在 words 中的单词加入窗口 Hash 表
if(window.containsKey(subRight))
window.put(subRight, window.get(subRight)+1);
else
window.put(subRight, 1);
// 当该单词在窗口中的出现次数多于在 words 中的出现次数时,
//不断删除窗口中最左边单词,直到次数相等
while(window.get(subRight) > wordsCount.get(subRight))
{
String subLeft = s.substring(left, left + w_len);
if(window.get(subLeft) == 1)
window.remove(subLeft);
else
window.put(subLeft, window.get(subLeft)-1);
left += w_len;
}
// 当窗口长度正好等于 words 总长度时,表示匹配成功,加入结果中
if(right+w_len-left == w_len*m)
ret.add(left);
}
right += w_len;
}
}
return ret;
}
};
浙公网安备 33010602011771号