LeetCode(30):與所有單詞相關聯的字串


Hard!

題目描述:

給定一個字符串 s 和一些長度相同的單詞 words。在 s 中找出可以恰好串聯 words 中所有單詞的子串的起始位置。

注意子串要與 words 中的單詞完全匹配,中間不能有其他字符,但不需要考慮 words 中單詞串聯的順序。

示例 1:

輸入:
  s = "barfoothefoobarman",
  words = ["foo","bar"]
輸出: [0,9]
解釋: 從索引 0 和 9 開始的子串分別是 "barfoor" 和 "foobar" 。
輸出的順序不重要, [9,0] 也是有效答案。

示例 2:

輸入:
  s = "wordgoodstudentgoodword",
  words = ["word","student"]
輸出: []

解題思路:

這道題讓我們求串聯所有單詞的子串,就是說給定一個長字符串,再給定幾個長度相同的單詞,讓我們找出串聯給定所有單詞的子串的起始位置,還是蠻有難度的一道題。這道題我們需要用到兩個哈希表,第一個哈希表先把所有的單詞存進去,然后從開頭開始一個個遍歷,停止條件為當剩余字符個數小於單詞集里所有字符的長度。這時候我們需要定義第二個哈希表,然后每次找出給定單詞長度的子串,看其是否在第一個哈希表里,如果沒有,則break,如果有,則加入第二個哈希表,但相同的詞只能出現一次,如果多了,也break。如果正好匹配完給定單詞集里所有的單詞,則把i存入結果中,具體參見代碼如下:

C++解法一:

 1 class Solution {
 2 public:
 3     vector<int> findSubstring(string s, vector<string>& words) {
 4         vector<int> res;
 5         if (s.empty() || words.empty()) return res;
 6         int n = words.size(), m = words[0].size();
 7         unordered_map<string, int> m1;
 8         for (auto &a : words) ++m1[a];
 9         for (int i = 0; i <= (int)s.size() - n * m; ++i) {
10             unordered_map<string, int> m2;
11             int j = 0; 
12             for (j = 0; j < n; ++j) {
13                 string t = s.substr(i + j * m, m);
14                 if (m1.find(t) == m1.end()) break;
15                 ++m2[t];
16                 if (m2[t] > m1[t]) break;
17             }
18             if (j == n) res.push_back(i);
19         }
20         return res;
21     }
22 };

這道題還有一種O(n)時間復雜度的解法,設計思路非常巧妙,但是感覺很難想出來。

這種方法不再是一個字符一個字符的遍歷,而是一個詞一個詞的遍歷,比如根據題目中的例子,字符串s的長度n為18,words數組中有兩個單詞(cnt=2),每個單詞的長度len均為3,那么遍歷的順序為0,3,6,8,12,15,然后偏移一個字符1,4,7,9,13,16,然后再偏移一個字符2,5,8,10,14,17,這樣就可以把所有情況都遍歷到。

還是先用一個哈希表m1來記錄words里的所有詞,然后我們從0開始遍歷,用left來記錄左邊界的位置,count表示當前已經匹配的單詞的個數。然后我們一個單詞一個單詞的遍歷,如果當前遍歷的到的單詞t在m1中存在,那么我們將其加入另一個哈希表m2中,如果在m2中個數小於等於m1中的個數,那么我們count自增1,如果大於了,那么需要做一些處理,比如下面這種情況, s = barfoofoo, words = {bar, foo, abc}, 我們給words中新加了一個abc,目的是為了遍歷到barfoo不會停止,那么當遍歷到第二foo的時候, m2[foo]=2, 而此時m1[foo]=1,這時候已經不連續了,所以我們要移動左邊界left的位置,我們先把第一個詞t1=bar取出來,然后將m2[t1]自減1,如果此時m2[t1]<m1[t1]了,說明一個匹配沒了,那么對應的count也要自減1,然后左邊界加上個len,這樣就可以了。

如果某個時刻count和cnt相等了,說明我們成功匹配了一個位置,那么將當前左邊界left存入結果res中,此時去掉最左邊的一個詞,同時count自減1,左邊界右移len,繼續匹配。如果我們匹配到一個不在m1中的詞,那么說明跟前面已經斷開了,我們重置m2,count為0,左邊界left移到j+len

C++解法二:

 1 class Solution {
 2 public:
 3     vector<int> findSubstring(string s, vector<string>& words) {
 4         if (s.empty() || words.empty()) return {};
 5         vector<int> res;
 6         int n = s.size(), cnt = words.size(), len = words[0].size();
 7         unordered_map<string, int> m1;
 8         for (string w : words) ++m1[w];
 9         for (int i = 0; i < len; ++i) {
10             int left = i, count = 0;
11             unordered_map<string, int> m2;
12             for (int j = i; j <= n - len; j += len) {
13                 string t = s.substr(j, len);
14                 if (m1.count(t)) {
15                     ++m2[t];
16                     if (m2[t] <= m1[t]) {
17                         ++count;
18                     } else {
19                         while (m2[t] > m1[t]) {
20                             string t1 = s.substr(left, len);
21                             --m2[t1];
22                             if (m2[t1] < m1[t1]) --count;
23                             left += len;
24                         }
25                     }
26                     if (count == cnt) {
27                         res.push_back(left);
28                         --m2[s.substr(left, len)];
29                         --count;
30                         left += len;
31                     }
32                 } else {
33                     m2.clear();
34                     count = 0;
35                     left = j + len;
36                 }
37             }
38         }
39         return res;
40     }
41 };

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM