一、概述
輸入一個字符串s和一個字符串數組words,words中的字符串等長,若s有一個子串,由words中所有元素構成,則輸出這個字串的第一個字符的下標。輸出所有該類型子串的下標。
要求有點繁瑣,舉例子就很簡單:
s = "barfoothefoobarman", words = ["foo","bar"]
那麼子串有barfoo,下標爲0;foobar,下標爲9。解答起來也很麻煩。
二、分析
我的代碼時空複雜度還不錯,因此就只分析我自己的了。
首先注意到words中元素的限制條件——長度相同,設置其爲l,那麼就可以根據這一點來進行遍歷:
第一輪:從s的第0個開始,每l個一組,與words中的元素比較;
第二輪:從s的第1個開始;第三輪,從s的第2個開始;...第l輪:從s的第l-1個開始。
然後就結束了。從第l個開始等價於從第0個開始。因此時間複雜度爲O(l*n),n爲s的長度。
然後要注意到words中的元素可以有重複的,最開始我就是沒注意到這點,用set去做,發現了之後所有代碼都要推倒從來,很是蛋疼。
對於每輪比較,我們維護一個hash表m,儲存當前words中還沒匹配的元素;維護一個指針tmp_ans,指向當前子串的頭部;維護一個整數now_len,表示當前子串的長度。當比較的時候,會出現三種情況:
①、s中的該子串在words中沒有對應的。那麼頭指針將指向該子串的下一個。now_len置零。m回覆初始狀態。
②、s中的該子串在word中有對應的,但是words中該子串對應的元素已經全部匹配完。那麼開始循環,頭指針對應的子串對應的m中的值+1,頭指針前移,長度減一;直到words中該子串對應的元素重新出現。
③、s中的該子串在word中有對應的,now_len與words的元素個數相等。那麼我們找到一個解。將這個解保存下來,頭指針前移;m增加,now_len減少。
代碼如下:
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> res;
if(s==""||words.size()==0)
return res;
unordered_map<string,int> m;
for(int i=0;i<words.size();++i)
if(words[i].size()>s.size())
return res;
else
++m[words[i]];
for(int i=0;i<words[0].size();++i)
{
int tmp_ans=i;
int now_len=0;
for(int j=i;j<=s.size()-words[0].size();j+=words[0].size())
{
if(m.find(s.substr(j,words[0].size()))==m.end())
{
while(tmp_ans!=j)
{
++m[s.substr(tmp_ans,words[0].size())];
tmp_ans+=words[0].size();
}
tmp_ans+=words[0].size();
now_len=0;
}
else
{
--m[s.substr(j,words[0].size())];
++now_len;
while(m[s.substr(j,words[0].size())]<0)
{
++m[s.substr(tmp_ans,words[0].size())];
tmp_ans+=words[0].size();
--now_len;
}
if(now_len==words.size())
{
res.push_back(tmp_ans);
++m[s.substr(tmp_ans,words[0].size())];
tmp_ans+=words[0].size();
--now_len;
}
}
}
while(now_len>0)
{
++m[s.substr(tmp_ans,words[0].size())];
tmp_ans+=words[0].size();
--now_len;
}
}
return res;
}
};
注意四點:
其一,當頭指針出現變化時,一定對應len和m的變化,不要忘了;
第二,每次j遍歷完,都要把m恢復原樣;
第三,用unorder_map而不是map,可以節省三倍時間;
第四,words中的元素長度如果比s大,可以直接返回。
三、總結
有些複雜的題目,主要就是words可以有重複元素,導致無法使用set,必須使用哈希表才行。這樣要維護多個變量和數據結構,就顯得有些麻煩。注意,只需要哈希表時不要用map,很拖後腿。