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"]
输出:[]
题解:
设 s
的长度为 n
,words
的长度为 m
,每个单词的长度为 w
。
使用滑动窗口,因为单词长度都相同,可以每次滑动一个单词长度。
根据 w
将 s
分成 w
组候选序列,对于每组序列能否由 words
构成,使用哈希表统计数目即可,见代码。
时间复杂度: O ( n ∗ w ) O(n * w) O(n∗w)
代码:
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
if ( !s.size() || !words.size() || !words[0].size() ) return {};
int n = s.size(), m = words.size(), w = words[0].size();
unordered_map<string, int> hash;
for( auto& it : words ) ++hash[it];
vector<int> ret;
string word;
for ( int i = 0; i < w; ++i ) {
unordered_map<string, int> wd;
int cnt = 0;//统计 wd 中存在几个单词在 hash 中也存在
for ( int j = i; j + w <= n; j += w ) {
if ( j >= i + m * w ) {
word = s.substr( j - m * w, w );
if ( --wd[word] < hash[word] ) --cnt;
}
word = s.substr(j, w);
++wd[word];
if ( wd[word] <= hash[word] ) ++cnt;
if ( cnt == m ) ret.push_back( j - (m - 1) * w );
}
}
return ret;
}
};
/*
时间:108ms,击败:70.05%
内存:41MB,击败:34.65%
*/
优化:可以看到上面一份代码中,不管 word
是不是 words
列表中的单词,都无脑加入或删除。对于 s="foobarthefoo"
和 words=['foo','bar,'foo']
来说,在匹配到 the
时,该单词不在 words
中,也就是说,前面窗口维护的元素没有用了,需要重置。于是可以考虑维护只存在 words
中的单词的窗口,下面给出优化代码:
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
if ( !s.size() || !words.size() || !words[0].size() ) return {};
int n = s.size(), m = words.size(), w = words[0].size();
unordered_map<string, int> hash;
for( auto& it : words ) ++hash[it];
vector<int> ret;
for ( int i = 0; i < w; ++i ) {
unordered_map<string, int> wd;
int cnt = 0, l = i;
for ( int j = i; j + w <= n; j += w ) {
auto word = s.substr(j, w);
if ( hash.count( word ) ) {
++wd[word];
if ( wd[word] <= hash[word] ) ++cnt;
else {
while ( wd[word] > hash[word] ) {
auto tmp = s.substr( l, w );
--wd[tmp];
if ( wd[tmp] < hash[tmp] ) --cnt;
l += w;
}
}
if ( cnt == m ) {
ret.push_back( l );
--wd[s.substr(l, w)];
--cnt;
l += w;
}
} else {
wd.clear();
cnt = 0;
l = j + w;
}
}
}
return ret;
}
};
/*
时间:40ms,击败:96.45%
内存:16.1MB,击败:86.31%
*/
上述代码每次取出一个长度为 w
的子串,插入或删除,每次操作时间复杂度为:
O
(
w
)
O(w)
O(w) ,可以考虑使用字符串哈希优化成
O
(
1
)
O(1)
O(1)。
时间复杂度: O ( n ) O(n) O(n)
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
using ULL = unsigned long long;
const int P = 131;
int n = s.size();
int m = words.size();
int w = words[0].size();
if ( !n || !m || !w ) return {};
if ( n < m * w ) return {};
vector<ULL> h( n + 1 );
ULL ans = 1;
for ( int i = 1; i <= n; ++i ) {
if ( i <= w ) ans *= P;
h[i] = h[i - 1] * P + s[i - 1];
}
unordered_map<ULL, int> hash;
for( auto& it : words ) {
ULL t = 0;
for ( auto& c : it ) t = t * P + c;
++hash[t];
}
vector<int> ret;
for ( int i = 1; i <= w; ++i ) {
unordered_map<ULL, int> wd;
int cnt = 0, l = i;
for ( int j = i; j + w <= n + 1; j += w ) {
auto word = h[j + w - 1] - h[j - 1] * ans;
if ( hash.count( word ) ) {
++wd[word];
if ( wd[word] <= hash[word] ) ++cnt;
else {
while ( wd[word] > hash[word] ) {
auto tmp = h[l + w - 1] - h[l - 1] * ans;
--wd[tmp];
if ( wd[tmp] < hash[tmp] ) --cnt;
l += w;
}
}
if ( cnt == m ) {
ret.push_back( l - 1 );
--wd[h[l + w - 1] - h[l - 1] * ans];
--cnt;
l += w;
}
} else {
wd.clear();
cnt = 0;
l = j + w;
}
}
}
return ret;
}
};
/*
时间:24ms,击败:99.70%
内存:13.1MB,击败:98.88%
*/
最后,贴一版本枚举 s
的每个字符,然后判断子串 s[i...i+m*w-1]
是否满足条件的代码,使用了字符串哈希,时间复杂度:
O
(
n
∗
m
)
O(n*m)
O(n∗m)
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
using ULL = unsigned long long;
const int P = 131;
if ( !s.size() || !words.size() ) return {};
int lens = s.size();
int n = words.size();
int l = words[0].size();
if ( lens < n * l ) return {};
vector<ULL> h( lens + 1 );
ULL ans = 1;
for ( int i = 1; i <= lens; ++i ) {
if ( i <= l ) ans *= P;
h[i] = h[i - 1] * P + s[i - 1];
}
unordered_map<ULL, int> hash;
for( auto& it : words ) {
ULL t = 0;
for ( auto& c : it ) t = t * P + c;
hash[t] += 1;
}
int tot = n * l;
unordered_map<ULL, int> window;
bool flag;
vector<int> ret;
for ( int i = 1; i <= lens - tot + 1; ++i ) {
window.clear();
flag = true;
for ( int j = i; j + l <= i + tot; j += l ) {
ULL tmp = h[j + l - 1] - h[j - 1] * ans;
++window[tmp];
if ( window[tmp] > hash[tmp] ) {
flag = false;
break;
}
}
if ( flag ) ret.push_back( i - 1 );
}
return ret;
}
};
/*
时间:124ms,击败:67.71%
内存:18.9MB,击败:71.09%
*/