给定一个非空字符串 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
,原因是vector
并不是一个真正的容器,这是来自Effective STL
的建议,所以我们可以使用bitset
或者vector
、deque
。
这个问题我们也可以通过回溯法来求解。我们需要直到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
我将该问题的其他语言版本添加到了我的GitHub Leetcode
如有问题,希望大家指出!!!