It dose not do to dwell on dreams and forget to live.
人不能因为依赖梦想而忘记生活。
问题描述
给定一个非空字符串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拆分成一些子串,并且判断这些子串是否都存在字典wordDict中。
我们定义dp[i]表示字符串的前i个字符经过拆分是否都存在于字典wordDict中。如果要求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我们需要一个个枚举,我们来看下最终代码
1public boolean wordBreak(String s, List<String> dict) {
2 boolean[] dp = new boolean[s.length() + 1];
3 for (int i = 1; i <= s.length(); i++) {
4 //枚举k的值
5 for (int k = 0; k <= i; k++) {
6 //如果往前截取全部字符串,我们直接判断子串[0,i-1]
7 //是否存在于字典wordDict中即可
8 if (k == i) {
9 if (dict.contains(s.substring(0, i))) {
10 dp[i] = true;
11 continue;
12 }
13 }
14 //递推公式
15 dp[i] = dp[i - k] && dict.contains(s.substring(i - k, i));
16 //如果dp[i]为true,说明前i个字符串结果拆解可以让他的所有子串
17 //都存在于字典wordDict中,直接终止内层循环,不用再计算dp[i]了。
18 if (dp[i]) {
19 break;
20 }
21 }
22 }
23 return dp[s.length()];
24}
上面代码有一个判断,就是截取的是前面全部字符串的时候要单独判断,其实当截取全部的时候我们只需要判断这个字符串是否存在于字典wordDict中即可,可以让dp[0]为true,dp[0]表示的是空字符串。这样代码会简洁很多,我们来看下
1public boolean wordBreak(String s, List<String> dict) {
2 boolean[] dp = new boolean[s.length() + 1];
3 dp[0] = true;//边界条件
4 for (int i = 1; i <= s.length(); i++) {
5 for (int j = 0; j < i; j++) {
6 dp[i] = dp[j] && dict.contains(s.substring(j, i));
7 if (dp[i]) {
8 break;
9 }
10 }
11 }
12 return dp[s.length()];
13}
这个和第一种写法不太一样,这个每次截取的方式如下图所示。
总结
这题我们也可以把它看作是一个完全背包问题,背包就是字符串s,而所选择的商品就是字典中的字符串,因为字典中的字符串可以选择多次并且没有限制,所以我们可以认为他是一个完全背包问题,完全背包问题我们以后会讲。其实这题还可以使用dfs来解决,由于这题写完的时候已经比较晚了,就留在明天讲吧。