题目描述
给定一个非空字符串 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”。
注意你可以重复使用字典中的单词。
动态规划
-
状态:可以设置dp[i]表示前i位是否可以用wordDict单词表示
-
状态转移方程:设j>i,若dp[i]为True,那么当i到j之间的字符也在字典中,则dp[j]也为True。
那么就可以得出方程
dp[j]=dp[i] && s[i:j] in wordDict (j>i 且j<=n)
-
由此得出以下代码实现
时间复杂度为 O ( n 2 ) O(n^2) O(n2)
空间复杂度为 O ( n ) O(n) O(n)
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
# 1. 初始化dp长度为字符串长度+1的False数组(其中dp[0]=True)
n = len(wordDict)
dp = (n + 1) * [False]
dp[0] = True
# 2. 从0到字符串长度开始遍历计算是否可以用wordDict表示,遍历索引为i
for i in range(n):
# 2.1 从i+1开始遍历后面的字符串,区间为[i+1,n+1),索引为j
for j in range(i + 1, n + 1):
# 2.2 若前i位字符可以用wordDict单词表示并且从i到j中的字符也在wordDict单词之中,则设置dp[j]为True
if s[i:j] in wordDict and dp[i] == True:
dp[j] = True
return dp[n + 1]
DFS
dfs是比较容易想到的方法
比如对于字符串"helloworld"能否break,就可以拆分为
- "h"是否是字典中单词,剩下的子串是否能break
- "he"是否是字典中单词,剩下的子串是否能break
- 以此类推
可以得到如下代码
def wordBreakMemo(s: str, wordDict: List[str]) -> bool:
n=len(s)
def wb(start: int)->bool:
if start == n:
return True
for i in range(start+1,n+1):
if s[start:i] in wordDict and wb(i):
return True
return False
return wb(0)
但是这个当碰到
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab,[“a”,“aa”,“aaa”,“aaaa”,“aaaaa”,“aaaaaa”,“aaaaaaa”,“aaaaaaaa”,“aaaaaaaaa”,“aaaaaaaaaa”]
便会超时。
分析原因是因为做了大量的重复计算,我们可以引入数组存储重复结果来优化。比如对第一次第二次都是从字典中取"a"和第一次从字典中取"aa",他们剩余子串结果是一样的。
于是得出以下代码
def wordBreakMemo(s: str, wordDict: List[str]) -> bool:
n=len(s)
memo=(n+1)*[None]
memo[n]=True
def wb(start: int)->bool:
if memo[start] is not None:
return memo[start]
for i in range(start+1,n+1):
if s[start:i] in wordDict and wb(i):
memo[start]=True
return True
memo[start]=False
return False
return wb(0)
BFS
DFS可以用,那么BFS自然也可以用。我们可以从一个字符开始扫描字符串,若是字符在字典中,那就放入队列。接着出列,重复上述操作。
def wordBreak(s: str, wordDict: List[str]) -> bool:
n = len(s)
queue = [0]
while len(queue) != 0:
i = queue.pop()
for j in range(i + 1, n + 1):
if s[i:j] in wordDict:
if j < n:
queue.append(j)
else:
return True
return False
在BFS也存在与DFS一致的问题,我们可以通过一个数组存储着BFS访问过的节点,若存在重复节点,则直接跳过。
def wordBreakMemo(s: str, wordDict: List[str]) -> bool:
n = len(s)
queue = [0]
visited=[]
while len(queue) != 0:
i = queue.pop()
if i not in visited:
visited.append(i)
for j in range(i + 1, n + 1):
if s[i:j] in wordDict:
if j < n:
queue.append(j)
else:
return True
return False
Ref
- https://leetcode-cn.com/problems/word-break
- https://leetcode-cn.com/problems/word-break/solution/dong-tai-gui-hua-ji-yi-hua-hui-su-zhu-xing-jie-shi/
- https://leetcode-cn.com/problems/word-break/solution/shou-hui-tu-jie-san-chong-fang-fa-dfs-bfs-dong-tai/