30. 串聯所有單詞的子串
給定一個字符串 s 和一些長度相同的單詞 words。找出 s 中恰好可以由 words 中所有單詞串聯形成的子串的起始位置。
注意子串要與 words 中的單詞完全匹配,中間不能有其他字符,但不需要考慮 words 中單詞串聯的順序。
解題思路: 先將題意轉換一種說法,題目給定一組長度相同的單詞,假設個數爲n,長度爲len,在字符串s中找一段長度爲 n*len
的子字符串,使得這個子字符串恰好由單詞words組成,那麼最自然的想法是,對字符串s每個長度爲n*len
的子字符串都進行判斷,但是起點在len(s)-n*len之後的子字符串不用判斷,因爲長度不夠(提前剪枝,降低時間複雜度),那麼剩下的就是如何設計函數來判斷子字符串是否恰好能由words串聯而成,有兩種方法:
- 由於字符串會重複,可以將words存在multiset中,然後在子字符串中以步進len截取子字符串,判斷是否在multiset中,若存在則將multiset中這個字符串erase掉,否則,若不存在,則判斷multiset是否爲空了,爲空說明已經找到串聯的字符串;
- 或者不使用multiset而使用hashmap,對words詞頻進行統計,同樣以步進len截取子字符串,若子字符串存在於hashmap中,則將hashmap減1,當hashmap對應項爲0時,erase,若子字符串不存在於hashmap中,則判斷hashmap是否爲空,若爲空,則表明已經找到串聯的字符串。
最後提交OJ發現,方法1 TLE了,而方法2險過,看來方法1使用的rb-tree複雜度有點高,以後算法題中能用hash儘量用hash。不過方法2對應的整個算法,時間複雜度爲,雖然網上有時間複雜度爲O(n),但是憑現在的水平有點難以理解,還是本題算法好理解一點。
class Solution {
public:
bool helper(string &s, int i, vector<string>& words) {
int len = words[0].size();
unordered_map<string, int> tmpcnt = wordsCnt;
for (int j = i; j + len <= s.size(); j += len) {
string tmp = s.substr(j, len);
if (tmpcnt.count(tmp) == 0) {
return tmpcnt.empty();
}
--tmpcnt[tmp];
if (tmpcnt[tmp] == 0) tmpcnt.erase(tmp);
}
return tmpcnt.empty();
}
vector<int> findSubstring(string s, vector<string>& words) {
if (s.empty() || words.empty() || words[0].empty()) return {};
vector<int> res;
for (auto &word : words) ++wordsCnt[word];
int n = words.size(), len = words[0].size();
for (int i = 0; i <= (int)s.size() - n * len; ++i) {
if (helper(s, i, words)) {
res.push_back(i);
}
}
return res;
}
private:
unordered_map<string, int> wordsCnt;
};
————————————
參考資料:
https://www.cnblogs.com/grandyang/p/4521224.html