【LeetCode】30. Substring with Concatenation of All Words題解

       給定一個字符串s和很多相同長度的字符串words,這些字符串可以有相同的,任務是在s裏找到一部分正好是words的全部字符串按任意順序組合起來,輸出符合條件的部分在s的起始位置。題意依舊是so easy,幾句話搞定的事,初看的時候感覺複雜度好高,這得比到海枯石爛啊,偷偷看了下大佬的DA,分享一下兩種解法。先貼原題。

You are given a string, s, and a list of words, words, that are all of the same length. Find all starting indices of substring(s) in s that is a concatenation of each word in words exactly once and without any intervening characters.

Example 1:

Input:
  s = "barfoothefoobarman",
  words = ["foo","bar"]
Output: [0,9]
Explanation: Substrings starting at index 0 and 9 are "barfoor" and "foobar" respectively.
The output order does not matter, returning [9,0] is fine too.

Example 2:

Input:
  s = "wordgoodgoodgoodbestword",
  words = ["word","good","best","word"]
Output: []

 一、挨個比較不成就無腦換下一個法

       想法其實很樸素,但是我題做得少一開始沒敢下手。依次從s[0],s[1],s[2]...開始找words中的字符串,如果不多不少全部找到,就成功找到一個位置,然後繼續從s的下一個位置找,如果多了少了或者中混進了其他字符串就失敗,還是從s的下一個位置找。舉個栗子:比如words字符串長度都是4,在s中從起點0開始比較,發現0-3符合,繼續4-7也符合,但是8-11不符合,於是退出,從起點1再依次比較......

       假設words中字符串長度都是len,需要注意的是(1)在s中取一段len後怎麼確定是否在words裏面,(不要求順序所以稍微增加了尋找難度),(2)又怎麼確定這一段是否重複出現了(words裏有重複的串,但是如果s裏的一部分重複次數超過words也是不行的)。

       不借助其他工具,我們可以用在s裏取len,然後遍歷words數組看下有木有,如果有,就把words[i]和word[n--]交換,那麼後面再遍歷words的時候就不會再看它了,這樣既解決了有沒有又解決了重不重複的問題,是一種比較好用且常見的手段。具體細節參看完整代碼:

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        vector<int> result;
        if (words.size() > 0){
		    int num = words.size(), len = words[0].size();
		    int ed = s.size() - num * len + 1;
		    for (int i = 0; i < ed; ++i){
			    int m = 0, n = num, k = i;
			    while (m < n){
                    //words[m][0] == s[k]似乎沒有用,刪掉一樣能跑,但是有它可以簡化很多比較
                    //測試用例有它是368ms,沒有是2944ms,差距很大
				    if (words[m][0] == s[k] && s.substr(k, len) == words[m]){
					    std::swap(words[m], words[--n]);
					    k += len; m = -1;
				    }
				    ++m;
				    if (0 == n) result.push_back(i);
			    }
		    }
        }
		return result;
    }
};

       這種方法思路和上面是一樣的,只是在words裏查找的時候藉助了STL裏的unordered_map容器,萌新也是做這道題剛知道這個神器,內部實現哈希散列,提供更快地查找功能,實測下來在對於這道題一般般吧,比上面的方法快一倍而已(176ms)。unordered_map的元素必須是成對的,一個是不允許重複也不能修改的key值,這裏是words的各個字符串,另一個就隨意了沒什麼限制,這裏用來記錄數量。先把words全部插進unordered_map B1裏,在取s的len長度子串查找時,再構造一個unordered_map B2,先在B1裏查找有木有,如果沒有就break,有的話記錄下數量,然後把子串插入B2,數量++,再看B2中此串的數量如果沒超過B1說明可以,直到取的子串數量和words裏一樣多,說明完全匹配,記錄下座標加到結果裏。具體細節參看完整代碼:

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        vector<int> result;
        if (s.empty() || words.empty()) return result;
        unordered_map<string,int> B1;
        for (string str : words)    B1[str]++;    //構造表1
        int num = words.size();int len = words[0].size();
        int ed = s.size()-num*len;
        unordered_map<string,int>::iterator it;
        int j,n1,n2;
        for (int i = 0;i <= ed;i++){
            unordered_map<string,int> B2;    //構造表2
            int temp = i+num*len;
            for (j = i;j < temp;j+=len){
                string str = s.substr(j,len);
                if ((it = B1.find(str)) == B1.end())  break;    //如果此串在表1裏沒有則退出
                n2 = ++B2[str];
                n1 = it->second;
                if (n2 > n1)    break;    //如果數量比表1的多也退出
            }
            if (j == temp)   result.push_back(i);    //不多不少全部都有則成功
        }
		return result;
    }
};

二、不成功也要比到s結尾只需len趟就搞定絕對不重複無用比較法

       上述兩種方法存在一個共同的問題,比如words字符串長度都是4,在s中從起點0開始比較,發現0-3符合,繼續4-7也符合,但是8-11不符合,於是退出從起點1再依次比較,等到起點爲4的時候又要重複比較一邊4-7和8-11。於是就有了這種辦法,可以避免重複比較。如上面的例子,當起點爲1,2,或3時並不會去重複比較4-7和8-11,只有起點間隔是4的整數倍的時候纔會重複。

       可以分爲len趟(words中字符串的長度),這樣每一趟之間都不會有重複。在具體的其中一趟,無論比較是否成功都會從s的頭比到尾。依然藉助unordered_map,用words構造一個B1,每一趟都構造一個和B1相同的B2,並用cnt初始化爲words字符串的數量。如果在s中取的子串在B2中有且數量大於0,則數量減一,表示已經匹配了一個,cnt也減1(由於unordered_map的特性,通過B1[]訪問的時候,如果要查找的字符串不在表裏,就會插進去),然後再取words所有字符串總長之前的一段子串,因爲這一段肯定已經超過可能匹配的範圍,把它的數量加回來,如果+1後大於0說明是words裏的串,cnt++(因爲words裏的串初始值都大於0,即使全部拿去匹配也只會是0,而不在words裏的串都是-1)。如果cnt==0,說明連續匹配上了words中的全部串(如果不連續,因爲上一句的cnt++,cnt不會等於0,只有連續匹配才能不斷cnt--),把座標加入result即可。測試用例耗時92ms,比前一種方法又快了大約一倍。具體細節參看完整代碼:

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        vector<int> result;
        if (s.empty() || words.empty()) return result;
        int n = s.size(), len = words[0].size(), total = words.size(), cnt = total;
        unordered_map<string, int> B1;
        for (string str : words) B1[str]++;
        for (int i = 0; i < len; i++) {    //分爲len趟比較,避免重複
            unordered_map<string, int> B2 = B1;
            cnt = total;
            for (int j = i; j + len <= n; j += len) {
                string str = s.substr(j, len);
                if (B2[str]-- > 0) cnt--;    //如果str在words裏面
                if (j - total*len >= 0) {                   
                    string out = s.substr(j - total*len, len);
                    if (++B2[out] > 0) cnt++;    //如果超出匹配範圍再把數量加回來
                }
                if (cnt == 0) result.push_back(j - (total-1)*len);   正好連續cnt個都匹配則成功              
            }
        }
        return result;
    }
};

初次接觸unordered_map和map,感覺特別強大。最後歡迎大家留言討論,如有錯誤或改進還請不吝賜教。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章