題目:給定乙個非空字串 s 和乙個包含非空單詞列表的字典 worddict,確定 s 是否可以被空格分割為乙個或多個在字典裡出現的單詞。你可以假設字典中無重複的單詞。
例如,給出
s ="leetcode"
,
dict =["leet", "code"]
。
返回 true 因為"leetcode"
可以被切分成"leet code"
。
分析:字串每乙個字元位置後都有兩種選擇,插or不插,類似於乙個揹包問題,考慮使用動態規劃,劃分為多個階段每個階段有插與不插兩種選擇。
動態規劃的方**:
動態規劃的關鍵是推導出遞推公式,有時候直接寫出遞推公式是非常困難的,在《挑戰程式設計》一書中給出了一種思考dp問題的通用方法(寫本篇文章的主要目的是為了介紹此書提出的關於動態規劃的思考方法)。
首先,使用遞迴,以遞迴的思路思考遞推關係更容易;
其次,分析遞迴,可以展開遞迴,分析遞迴的重複計算,將重複計算儲存起來,優化遞迴;
最後,分析遞迴+記憶陣列,從記憶陣列和遞迴關係中找出遞推關係。
解題:1.遞迴
class solution(object):
def wordbreak(self, s, worddict):
""":type s: str
:type worddict: list[str]
:rtype: bool
"""return self._wordbreak(s,worddict,0,-1)
def _wordbreak(self,s,worddict,i,lastsplitpos):
if i>=len(s)-1:
return s[lastsplitpos+1:i+1] in worddict
else:
#若i位置和上一次插入位置不構成單詞則不能插入
if s[lastsplitpos+1:i+1] not in worddict:
return self._wordbreak(s,worddict,i+1,lastsplitpos)
else:
return self._wordbreak(s,worddict,i+1,i) or self._wordbreak(s,worddict,i+1,lastsplitpos)
i代表階段,決策是否給s[i]後插入空格。lastsplitpos代表上一次插入空格位置。因此,若s[lastsplitpos+1:i+1] not in worddict,則第i階段不能插入空格。否則,則有兩種選擇,插入空格則lastsplitpos=i,不插則lastsplitpos.以上**提交leetcode超時,下一步分析重複。
2.記憶
class solution(object):
def wordbreak(self, s, worddict):
""":type s: str
:type worddict: list[str]
:rtype: bool
"""dp=[-1]*len(s)
if s in worddict:
dp[0]=true
return self._wordbreak(s,worddict,0,-1,dp)
def _wordbreak(self,s,worddict,i,lastsplitpos,dp):
#分析遞迴找重複
if dp[lastsplitpos+1]!=-1:
return dp[lastsplitpos+1]
if i>=len(s)-1:
return s[lastsplitpos+1:i+1] in worddict
else:
#若s[lastsplitpos+1:i+1] not in worddict則此位置不能插入空格
if s[lastsplitpos+1:i+1] not in worddict:
dp[lastsplitpos+1]=self._wordbreak(s,worddict,i+1,lastsplitpos,dp)
return dp[lastsplitpos+1]
else:
dp[i+1]=self._wordbreak(s,worddict,i+1,i,dp)
dp[lastsplitpos+1]=self._wordbreak(s,worddict,i+1,lastsplitpos,dp)
return dp[i+1] or dp[lastsplitpos+1]
假設輸入字串為"leetcode",展開遞迴分析
如圖,左邊代表此分支切割,右邊代表此分支不切割,紅圈位置出現了重複計算。因此使用dp儲存重複計算,dp[lastsplitpos],代表從lastsplitpos位置切分後,s[lastsplitpos+1:len(s)]串是否存在切分滿足題目要求,提交**,通過。
3.遞推公式
if s[lastsplitpos+1:i+1] not in worddict:
dp[i][lastsplitpos]= dp[i+1][lastsplitpos]
else:
dp[i][lastsplitpos]= dp[i+1][lastsplitpos] or dp[i+1][i]
通過遞迴+記憶我們發現重點變數有兩個,i和lastsplitpos。i代表了第i個階段,lastsplitpos是上一次切分位置。
dp[i][lastsplitpos]代表了第i個階段,lastsplitpos上一次切分位置,s[lastsplitpos+1:len(s)]是否滿足題目要求.
以輸入leet,字典為l,e,et為例,依賴從0-3階段,遞推從3-0階段最終dp[0][-1]為答案,注意陣列下標沒有-1,這裡是為了方便和遞迴比較.
補充:寫完部落格發表後,看到了乙個關聯的部落格 給出的解法更簡單,這個解法的思路使用的是字串問題最常用的dp思路,即從子串遞推到全部串,用乙個陣列記錄,dp[i]表示從s[0:i]是否滿足題目要求,一直遞推到dp[len(s)].
總結:當不能直接寫出動態規劃的遞推式時,可以採用遞迴+記憶陣列的方式。
字串dp的一般思路是從解決子s[0:i]問題,遞推到解決s[0:i+1]問題
動態規劃解決的是重疊子問題,即解決問題a需要通過子問題b來解決,而解決b要通過解決b的子問題c來解決,一環套一環。重疊子問題會出現重複計算,因此儲存以前的計算結果.
leetcode139 單詞拆分
給定乙個非空字串 s 和乙個包含非空單詞列表的字典 worddict,判定 s 是否可以被空格拆分為乙個或多個在字典中出現的單詞。說明 拆分時可以重複使用字典中的單詞。你可以假設字典中沒有重複的單詞。示例 1 輸入 s leetcode worddict leet code 輸出 true 解釋 返...
leetcode139 單詞拆分
給定乙個非空字串 s 和乙個包含非空單詞列表的字典 worddict,判定 s 是否可以被空格拆分為乙個或多個在字典 現的單詞。動態規劃,dp i 表示前i個字元能否被拆分 class solution def wordbreak self,s str,worddict list str bool ...
leetcode 139單詞拆分
單詞拆分 給定乙個非空字串 s 和乙個包含非空單詞列表的字典 worddict,判定 s 是否可以被空格拆分為乙個或多個在字典 現的單詞。說明 示例 1 輸入 s leetcode worddict leet code 輸出 true解釋 返回 true 因為 leetcode 可以被拆分成 lee...