单纯的想记录下这个问题,解决整个问题的思路。如果想要快速的看,就直接看dp的或者1.2 带有记忆的递归部分.
问题定义
leetcode 139 : 是否给定的句子s能够被拆分成字典wordDict里面的内容。如下例子 leetcode
能被拆分为leet
和code
,因此,结果返回为True
。
s = "leetcode", wordDict = ["leet", "code"]
1. 基于递归的思路
1.1 暴力递归
这是最能够想到的方法,整体的思路为:如果s是以wordDict中的任意一个单词开头的,只要保证s[len(w):]之后满足可以被拆分,那么么整个s就是可以被拆分的。基于这个思想,有如下代码:
def wordBreak1(self, s, wordDict):
# dfs 暴力:毫无疑问超时 T_T
if not s or s in wordDict:
return True
for w in wordDict:
if s.startswith(w): # 如果s是以wordDict中的任意一个单词开头的
if self.wordBreak(s[len(w):],wordDict): # 只要保证s[len(w):]之后满足可以被拆分
return True
return False
1.2 带有记忆力的递归
显然,上面的超时了…,fine,我们想别的办法。我们给它个记忆。带有记忆的递归实际分为两种,一种可以将memo[s]用来记住访问过的字符串是否可以被拆分,另外一种memo[i]用来记住范围为[0, i)的子字符串是否可以拆分或者是[i,len(s))的字符串是否可以背拆分。基于这两种思路,我们有以下的做法。
- memo[s]: 访问过的字符串s是否可以被拆分
def wordBreak2(self, s, wordDict):
# 定义长度为len(s) memo[i]定义为范围为[0, i)的子字符串是否可以拆分,
if not s or not wordDict:
return False
memo = {'':True} # 因为面可能会有访问s[len(s):],说明前面的s已经可分了,因此其为True
def dfs_memo(s):
if s in memo: return memo[s]
if s in wordDict:
memo[s] = True
return True
for w in wordDict:
if s.startswith(w):
if dfs_memo(s[len(w):]): # 这里理解下memo = {'':True}
return True
else:
memo[s[len(w)]] = False
memo[s] = False
return False
return dfs_memo(s)
-
memo[i]:(或许上面的幅图可以帮助理解~)
- dp[i]:从开始位置0到i即s[:i]是可分的
def wordBreak3(self, s, wordDict): # 定义长度为len(s) memo[i]定义为范围为[0, i)的子字符串是否可以拆分, if not s or not wordDict: return False memo = {} def dfs_memo(start): if start in memo: return memo[start] if s[start:] in wordDict: memo[start] = True return True for w in wordDict: if s[start:].startswith(w): if dfs_memo(start+len(w)): return True else: memo[start] = False memo[start] = False return False return dfs_memo(0)
- dp[i]:从位置i到s结束即s[i:]是可分的
def wordBreak4(self, s, wordDict): # 定义长度为len(s) memo[i]定义为范围为[i:]的子字符串是否可以拆分,(意味着前面的是可以拆分的) if not s or not wordDict: return False memo = {} def dfs_memo(end): if end in memo: return memo[end] if s[:end] in wordDict: # 注意这是是从头0到end在wordDict种 memo[end] = True return True for w in wordDict: if s[:end].endswith(w): # 如果s[:end] 是以w结束的,那么才可以将start往前移动,注意这离的 endswith if dfs_memo(end-len(w)): return True else: memo[end] = False memo[end] = False return False return dfs_memo(len(s)) # 注意开始的位置
2. 动态规划
写这个题目参考这位大佬的文章思考的,里面有很多总结挺不错,(小菜鸟不知正确与否,感觉是很有道理的)
这种使用记忆数组memo的递归写法,和使用dp数组的迭代写法,乃解题的两大神器,凡是能用dp解的题,一般也有用记忆数组的递归解法。
对于本题:dp[i]表示范围[0, i)内的子串可以被拆分,注意这里dp数组的长度比s串的长度大1,是因为我们要handle空串的情况,我们初始化dp[0]为true,然后开始遍历。dp[i]为True,且s[i:j] in wordDict,可以将dp[j]标记为True
def wordBreak_dp2(self, s, wordDict):
dp = [False] * (len(s)+1)
dp[0] = True
for i in range(len(s)):
if dp[i]:
for j in range(i,len(s)): # 可以从dp的角度想以len(s)作为结束点
if s[i:j+1] in wordDict:
dp[j+1] = True
return dp[-1]
3. BFS
…emmmmm…还未看?…可以去那位大佬或者leetcode上看