Leetcode 139:單詞拆分(最詳細的解法!!!)
給定一個非空字串 s 和一個包含非空單詞列表的字典 wordDict,判定 s 是否可以被空格拆分為一個或多個在字典中出現的單詞。
說明:
- 拆分時可以重複使用字典中的單詞。
- 你可以假設字典中沒有重複的單詞。
示例 1:
輸入: s = "leetcode", wordDict = ["leet", "code"]
輸出: true
解釋: 返回 true 因為 "leetcode" 可以被拆分成 "leet code"。
示例 2:
輸入: s = "applepenapple", wordDict = ["apple", "pen"] 輸出: true 解釋: 返回 true 因為 "applepenapple" 可以被拆分成 "apple pen apple"。 注意你可以重複使用字典中的單詞。
示例 3:
輸入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
輸出: false
解題思路
首先想到的是暴力破解,我們通過索引遍歷字串,測試本次遍歷到的s[pre:cur]
是不是wordDict
中的字串,不是的話cur+1
繼續判斷,否則我們pre=cur
,直到cur=len(s)
。
class Solution:
def wordBreak(self, s, wordDict):
"""
:type s: str
:type wordDict: List[str]
:rtype: bool
"""
len_s = len(s)
pre, cur = 0, 1
while cur != len_s:
if s[pre:cur] in wordDict:
pre = cur
else:
cur += 1
if s[pre:cur] in wordDict:
return True
return False
當然這裡我們可以通過將wordDict
儲存為dict
結構來加速。但是這種思路是錯的,我們使用這種貪心策略,會忽略這樣的問題。
s = "aaaaaaa"
wordDict = ["aaaa","aaa"]
我們相當於每次取最短,結果就是["aaa", "aaa", "a"]
。
實際上這個問題和之前的 Leetcode 300:最長上升子序列(最詳細的解法!!!) 很類似。我們同樣可以通過動態規劃的方法解決這個問題。我們定義函式f(i)
,表示s[0:i]
是否可以被拆分成字典中的單詞。所以我們需要遍歷字典中的單詞,例如
i = 7
s = "aaaaaaa"
wordDict = ["aaaa","aaa"]
我們遍歷到第一個"aaaa"
,此時我們要知道f(7)
是否成立,我們只需要知道f(3)
是否可以成立即可,如果f(3)
成立,我們這個時候只需要將"aaaa"
放入即可。而我們需要知道f(3)
是否可以成立,我們就需要直到f(-1)
能否成立,但是-1
超出了邊界,所以我們判斷"aaa"
能否放入,也就是我們判斷f(0)
能否成立即可。而我們直到對於空字串來說,直到我們單詞字典中不選出單詞即可,所以f(0)=True
。所以按照這個過程,我們可以很快寫出下面的程式碼
class Solution:
def wordBreak(self, s, wordDict):
"""
:type s: str
:type wordDict: List[str]
:rtype: bool
"""
len_s = len(s)
mem = [False]*(len_s+1)
mem[0] = True
for i in range(1, len_s + 1):
for word in wordDict:
if i >= len(word) and mem[i - len(word)] \
and word == s[i-len(word):i]:
mem[i] = True
return mem[-1]
上面這個程式碼存在一些細節上的優化,當我們mem[i]=Ture
後,我們可以直接退出迴圈了。另外我們可以將wordDict
做成一個包含單詞長度的字典
class Solution:
def wordBreak(self, s, wordDict):
"""
:type s: str
:type wordDict: List[str]
:rtype: bool
"""
len_s = len(s)
mem = [False]*(len_s+1)
mem[0] = True
tmpDict = dict((i,len(i)) for i in wordDict)
for i in range(1, len_s + 1):
for word in wordDict:
if i >= tmpDict[word] and mem[i - tmpDict[word]] \
and word == s[i-tmpDict[word]:i]:
mem[i] = True
break
return mem[-1]
上述程式碼在c++
實現的過程中最好不要使用vector<bool>
,原因是vector<bool>
並不是一個真正的容器,這是來自Effective STL
的建議,所以我們可以使用bitset
或者vector<int>
、deque<int>
。
這個問題我們也可以通過回溯法來求解。我們需要直到s
能否分割,那麼我們只需要知道s[index:]
能否分割,並且s[0:index]
是wordDict
中的單詞。如果index==len(s)
,我們就返回true
class Solution:
def wordBreak(self, s, wordDict):
"""
:type s: str
:type wordDict: List[str]
:rtype: bool
"""
return self._wordBreak(s, set(wordDict), 0)
def _wordBreak(self, s, words, start):
if start == len(s):
return True
for i in range(start + 1, len(s) + 1):
sub = s[start:i]
if sub in words and self._wordBreak(s, words, i):
return True
return False
同樣,我們可以通過記憶化搜尋的方法來優化這個問題。
class Solution:
def wordBreak(self, s, wordDict):
"""
:type s: str
:type wordDict: List[str]
:rtype: bool
"""
return self._wordBreak(s, set(wordDict), 0, set())
def _wordBreak(self, s, words, start, mem):
if start == len(s):
return True
if start in mem:
return False
for i in range(start + 1, len(s) + 1):
if i in mem:
continue
sub = s[start:i]
if sub in words and self._wordBreak(s, words, i, mem):
return True
mem.add(start)
return False
如有問題,希望大家指出!!!