Leetcode 30:與所有單詞相關聯的字串(超詳細的解法!!!)
給定一個字串 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"] 輸出: []
解題思路
我們首先想到的解法當然是暴力破解。我們將words
的所有排列情況列出來,為了避免重複元素,我們應該使用一個set
去存放結果。然後我們遍歷set
中的所有元素,檢視元素在s
中的位置,並將位置存放到list
中。
class Solution:
def findSubstring(self, s, words):
"""
:type s: str
:type words: List[str]
:rtype: List[int]
"""
res = list()
if not words:
return res
for word in set(itertools.permutations(words)):
tmp_word = ''.join(word)
i = s.find(tmp_word)
while i != -1:
res.append(i)
i = s.find(tmp_word, i + 1)
return res
提交程式碼後提示我們Memory Limit Exceeded
。我們遺漏了一個條件長度相同的單詞 words。所以我們就有了如下的策略,我們可以先將words
中的元素存放到dict
中,對於例1來說就是
此時我們的目標就是匹配dict
中的所有字串,我們從頭開始
我們發現此時匹配了bar
,所以我們此時應該移動len(words[0])
的步數
我們發現此時foo
也匹配了,所以此時dict
中的全部元素都匹配成功,我們就要記錄開始的index=0
。我們現在從index=1
開始匹配
我們發現第一個字串就匹配失敗,我們接著從index=2
開始也是失敗,我們接從index=3
開始
我們發現此時第一個匹配成功,所以我們此時應該移動len(words[0])
我們發現此時匹配失敗,所以我們就要從index=4
,依次往後。
class Solution:
def findSubstring(self, s, words):
"""
:type s: str
:type words: List[str]
:rtype: List[int]
"""
if not words:
return []
words_dict = collections.defaultdict(int)
for word in words:
words_dict[word] += 1
s_len, words_len, word_len, res = len(s), len(words), len(words[0]), list()
for i in range(s_len - words_len*word_len + 1):
num, has_words = 0, collections.defaultdict(int)
while num < words_len:
word = s[i+num*word_len:i+(num+1)*word_len]
if word not in words_dict:
break
has_words[word] += 1
if has_words[word] > words_dict[word]:
break
num += 1
if num == words_len:
res.append(i)
return res
上述程式碼提交後就獲得了accept
。你可能已經注意到了上述演算法中存在的一些缺陷,例如當我們的words
很長時,我們n-1
個word
都匹配成功了,但是就最後一個匹配失敗,我們就要index++
開始,實際上這是有問題的,我們此時應該繼續從失敗的單詞後繼續開始匹配,直到這一輪匹配完再從index++
開始,例如
我們發現此時匹配失敗,我們應該從the
後,也就是foo
開始將剩餘部分匹配完。
這樣我們就充分利用了我們之前儲存的資訊。為了更好地說明問題,我們取這樣的一個例子s = "barfoobarfoobarfoofoo"
,words = ["foo","bar","foo"]
。我們來看一下具體實現步驟
我們發現此時匹配到了一個單詞bar
,所以此時我們前進len(words[0])
步,並且將此時匹配到的單詞加入到一個臨時的字典中存放。
我們發現此時匹配到了一個單詞foo
,所以此時我們前進len(words[0])
步,並且將此時匹配到的單詞加入到一個臨時的字典中存放。
我們發現此時匹配到了一個單詞bar
,但是此時bar
的數量已經超過了words
中的,所以我們此時要將第一個bar
彈出。
我們發現此時匹配到的單詞the
不在words
中,所以此時我們前進len(words[0])
步,並且將臨時字典清空。
我們發現此時匹配到了一個單詞bar
,所以此時我們前進len(words[0])
步,並且將此時匹配到的單詞加入到一個臨時的字典中存放。
我們發現此時匹配到了一個單詞foo
,所以此時我們前進len(words[0])
步,並且將此時匹配到的單詞加入到一個臨時的字典中存放。
我們發現此時匹配到了一個單詞foo
,並且將此時匹配到的單詞加入到一個臨時的字典中存放,我們發現此時多有單詞匹配成功,我們就將index=12
加入到我們的結果中。接著我們再從index=1...len(words[0])-1
開始遍歷即可。程式碼如下
class Solution:
def findSubstring(self, s, words):
"""
:type s: str
:type words: List[str]
:rtype: List[int]
"""
if not words:
return []
words_dict = collections.defaultdict(int)
for word in words:
words_dict[word] += 1
s_len, words_len, word_len, res = len(s), len(words), len(words[0]), list()
for k in range(word_len):
has_words, num = collections.defaultdict(int), 0
for i in range(k, s_len, word_len):
word = s[i:i + word_len]
if word in words_dict:
num += 1
has_words[word] += 1
while has_words[word] > words_dict[word]:
pos = i - word_len*(num - 1)
rem_word = s[pos:pos + word_len]
has_words[rem_word] -= 1
num -= 1
else:
has_words.clear()
num = 0
if num == words_len:
res.append(i - word_len*(num - 1))
return res
我將該問題的其他語言版本新增到了我的GitHub Leetcode
如有問題,希望大家指出!!!