Word Break
此题是要求判断字符串s是否能被字典里面的词完整拆分,字典的词可以无限次使用
思路:
这道题一开始的想法是认为是一个区间类动态规划从两边分别拆词,然后不断从大到小递归到最后没有字符串就可以判断,但是后来发现实现上在区间的判定上有问题,且对于LeetCode上面最大的那个全是a的case效果明显不理想。后来参考博客上面的做法发现其实就是一道很简单的接龙型DP,只需要根据字符串长度建一个对应的一维数组,每一格记录[0-i]区间是否可以被完全拆分,其思路就跟LIS一模一样,外层循环每到一个位置就从头开始一直到i进行一个内部循环检查是否有[j-i]段的字符串在wordDict中可以被找到(这也是这道题一个巧妙的地方,不是拿字典的词一个个去找字符串,而是字符串分一段来字典里面找)下面是代码
class Solution(object):
def wordBreak(self, s, wordDict):
"""
:type s: str
:type wordDict: List[str]
:rtype: bool
"""
n = len(s)
res = [False] * (n + 1)
res[0] = True
for i in xrange(1, n + 1): # 坐标的问题比较tricky,但不是这道题的关键
for j in xrange(1, i + 1):
if s[j - 1 : i] in wordDict and res[j - 1]: # 只要有一个字段在字典里且剩下的部分可被拆分就break
res[i] = True
break
return res[n]
上面这种思路实际上是有三个循环的(in关键字实际上就是一个循环),如果将wordDict转为一个哈希表,这里速度变成O(n^2) (不考虑哈希字符串比对的时间)
class Solution(object):
def wordBreak(self, s, wordDict):
"""
:type s: str
:type wordDict: List[str]
:rtype: bool
"""
n = len(s)
dp = [False] * n
wordList = set(wordDict)
for i in xrange(n):
for j in xrange(i + 1):
if s[j : i + 1] in wordList:
dp[i] = dp[j - 1] if j >= 1 else True
if dp[i]:
break
return dp[n - 1]
当然记忆化搜索也是可行的,这里用了一个memo数组表示从当前下标到字符串终点是否可以被wordDict内的词拆分
class Solution(object):
def wordBreak(self, s, wordDict):
"""
:type s: str
:type wordDict: List[str]
:rtype: bool
"""
return self.helper(s, 0, len(s), set(wordDict), [None] * len(s))
def helper(self, string, start, end, wordDict, memo):
if start >= end:
return True
if memo[start] != None:
return memo[start]
for i in xrange(start, end):
if string[start : i + 1] in wordDict and self.helper(string, i + 1, end, wordDict, memo):
memo[start] = True
return memo[start]
memo[start] = False
return memo[start]
此题还可以用BFS的方法做,不过也是需要有一个memo来备忘才行
Word Break II
这道题的case太狗,简而言之,使用DFS或者DP的任意一种方法都过不了,因为有一个超长的input,LC的solution里面的解法也只有记忆化搜索一种方法能过(想不通为什么递推的DP不能过而备忘的可以过,不太合常理),如果像解法里这样one pass就过的方法想不出来,其实可以基于word break的那个方法继续做,我们可以把word break里最后的那个矩阵拿出来继续利用,首先这个矩阵记录的就是0 到 i之间的子字符串能否被wordDict里面的词拼凑出来,那我们可以利用这个矩阵继续进行DFS,利用它当一个lookup table,再加上开始的hashset一起就可以得到正确答案,另外因为那个大case的存在,同时也是节省时间的一个很好的方法,我们在dp完成后,可以先检查这个词是不是能被完全break,如果不行我们就不用浪费时间进行DFS而是直接返回空列表就好
class Solution(object):
def wordBreak(self, s, wordDict):
"""
:type s: str
:type wordDict: List[str]
:rtype: List[str]
"""
n = len(s)
dp = [False] * n
dp[0] = False
wordList = set(wordDict)
for i in xrange(n):
for j in xrange(i + 1):
if s[j : i + 1] in wordList:
dp[i] = dp[j - 1] if j >= 1 else True
if dp[i]:
break
if dp[n - 1] == False:
return []
result = []
self.helper(s, 0, len(s), '', result, dp, wordList)
return result
def helper(self, string, start, end, tmp, res, ref, checker):
if start >= end:
res.append(tmp[1:])
return
for i in xrange(start, end):
if (start == 0 or ref[start - 1]) and string[start : i + 1] in checker:
self.helper(string, i + 1, end, tmp + ' ' + string[start : i + 1], res, ref, checker)
此题还有备忘的方法可以不用额外检查one pass AC