题目
给定一个非空字符串 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)->完全背包
并且不用考虑组合还是排列,只需要判断是否可以组成字符串s
动规五部曲:
- 确定dp数组和下标含义
dp[i]
表示字符串长度为i
,dp[i] = true
表示可以拆分成功 - 确定递推公式
如果dp[j]
为true
,j < i
,并且下一个单词,即在区间[j,i]
的字串,出现在字典中,那么dp[i]
一定也为true
所以递推公式为:
if([j, i] 这个区间的子串出现在字典里 && dp[j]是true) 那么 dp[i] = true
- 初始化dp数组
从递推公式中可以看出,dp[i]
的状态取决于dp[j]
是否为true
,那么dp[0] = true
,否则后面无法进行递推
但是dp[0]
本身没有意义,因为题目明确说了字符串非空,不会出现i=0
的情况,初始化dp[0]
完全是为了推导公式;
其他所有下标非0
的dp[i]
初始化为false
,只要没有被覆盖,说明不可拆分 - 确定遍历顺序
不强调组合还是排列,本题顺序都可以
但是本题有特殊性,因为是子串,最好遍历背包放在外循环,遍历物品放在内循环,如果是外层遍历物品,内层遍历背包,就需要把所有的子串预先放在一个容器中(不理解的话,有个很简单的方法,就是这个顺序试一下,AC不了果断换个顺序遍历) - 举例推导dp数组
以输入: s = “leetcode”, wordDict = [“leet”, “code”]为例,dp状态如图:
java代码如下:
class Solution {
public boolean wordBreak(String s, List<String> wordDict){
int n = s.length();
boolean[] dp = new boolean[n+1];
dp[0] = true;
for(int i = 1; i <= n; i++){//先遍历背包,因为dp数组需要取到n+1,所以i要取到n
for(int j = 0; j < i; j++){//遍历物品,且物品容量要小于背包容量
if(wordDict.contains(s.substring(j,i)) && dp[j]){//如果前j个字符的子串可以拆分成功,并且j到i的子串也在字典中的话
dp[i] = true;//表示长度为i的字符串可以被拆分成功
}
}
}
return dp[n];
}
}