给你一个字符串 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
提示:
1 <= s.length <= 300
1 <= wordDict.length <= 1000
1 <= wordDict[i].length <= 20
s 和 wordDict[i]仅有小写英文字母组成
wordDict中的所有字符串互不相同
此题属于背包问题
解法:动态规划
1.初始化 dp=[False,False,⋯,False],长度为n+1。n为字符串长度。dp[i]表示s的前i位是否可以用wordDict中的单词表示。
2.初始化dp[0]=True,空字符可以被表示。
3.遍历字符串的所有子串,遍历开始索引i,遍历区间 [0,n):
遍历结束索引j,遍历区间[i+1,n+1):
若dp[i]=True且s[i,⋯,j)在wordlist中:dp[j]=True。
解释:dp[i]=True说明s的前i位可以用wordDict表示,则s**[i,⋯,j)出现在wordDict中,说明s的前j位可以表示**。
4.返回dp[n]
复杂度分析:
时间复杂度:O(n^2)
空间复杂度:O(n)
代码实现:
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
n=len(s)
dp=[False]*(n+1) # 初始化dp=[False,...,False],长度为n+1,n为字符串长度。dp[i]表示s的前i位是否可以用wordDict中的单词表示。
dp[0]=True # 初始化dp[0]=True,空字符串可以被表示
for i in range(n):
for j in range(i+1,n+1):
if(dp[i] and (s[i:j] in wordDict)):
print('i:',i)
print('j:',j)
print('s[i:j]:',s[i:j])
dp[j]=True
return dp[-1] # dp[n]和dp[-1]是一样的
类完全背包问题:s->背包 wordDict->物品
1.状态定义:dp[i]为考虑wordDict所有物品是否能凑成s[0,i-1]
2.状态转移:考虑新的s[i-1],其转移为true的途径为dp[j],其中j=[0,i-1]
若dp[j]为true(说明s[0,j-1]可以凑成)并且,s[j,i-1]存在于wordDict中,就可以转移为true
3.初始化:dp[0]=true,否则后面的永远也凑不成;而除了dp[0]其余均为false,说明没转移到就是凑不成为false
4.遍历顺序:这里只是要求能否凑成,因此背包容量与物品的遍历顺序无所谓,但是容量遍历必须正序
但是若先遍历物品则转移后的状态不符合dp[i]的定义(考虑所有的物品)
因此要先遍历背包容量,再遍历物品;同时这里不是单纯遍历物品,而是遍历前面状态是否转移过来
因此是遍历s的前面的分割线j∈[0,i-1]
5.返回形式:返回dp[n]就是所求