问题描述
给定一个非空字符串s和一个包含非空单词的列表wordDict,判定s是否可以被空格拆分为一个或者多个在字典中出现的单词。
说明
1、拆分时可以重复使用字典中的单词
2、可以假设字典中没有重复的单词
示例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字符串拆分成若干子串,并且判断这些子串是否都存在于字典wordDict中。
可以定义一个dp[i] 表示字符串的前i个字符经过拆分都是存在于字典中的单词。如果要求dp[i],需要往前截取k个字符,判断子串[i-k+1,i]是否存在于字典wordDict中,并且前面[0,i-k] 子串拆分的子串也是否都存在于WordDict中,如下图
前面的[0,i-k]子串拆分的子串是否存在于wordDict中只需要判断dp[i-k]就可以了,而子串[i-k_1,i] 是否存在于字典wordDict中需要查找,所以动态规划的递推公式很容易列出来。
dp[i] = dp[i-k]&&dict.contains(s.substring(i-k,i))
代码中的k需要一个个的进行枚举,最终代码如下
public boolean wordBreak(String s,List<String> dict){
boolean[] dp =new boolean[s.length()+1]
for(int i = 1;i<=s.length();i++){
//枚举k的值
for(int k = 0;k<=i;k++){
//如果往前截取全部的字符串,直接判断子串[0,i-1]
// 是否存在于字典wordDict中即可
if(k==i){
if(dict.contains(s.substring(0,i))){
dp[i] = true;
continue;
}
}
//递推公式
dp[i] = dp[i-k]&&dict.contains(s.substring(i-k,i));
// 如果dp[i] 为true,说明前i个字符串结果拆解可以让他的所有子串
// 都存在于字典wordDict中,直接终止内层循环,不用再计算dp[i]了。
if(dp[i]){
break;
}
}
}
return dp[s.lenght()];
}
上面代码有一个判断,就是截取的是前面全部字符串的时候需要单独判断,其实当截取全部的时候只需要判断这个字符串是否存在于字典中,可以让dp[0] 为true,dp[0] 表示的是空字符串,代码如下
public boolean wordBreak(String s,List<String> dict){
boolean[] dp =new boolean[s.length()+1]
dp[0] = true;
for(int i = 1;i<=s.length();i++){
//枚举k的值
for(int k = 0;k<=i;k++){
dp[i] = dp[i-k]&&dict.contains(s.substring(i-k,i));
if(dp[i]){
break;
}
}
}
return dp[s.lenght()];
}
逻辑如下图所示
总结
这个问题可以看做一个完全背包问题,背包就是字符串s,而所选择的商品就是字典中的字符串,因为字典中的字符串可以选择多次并且没有限制,所以可以认为他是一个完全背包问题,完全背包问题在后续的分享中会有,这个问题还可以用dfs来解决。