139. 单词拆分
难度 中等
给你一个字符串 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
中的所有字符串 互不相同
题解
这道题是一道搜索题目,如何搜索到给出的字符串是这道题的关键。一开始就想使用深搜去搜索,如果能搜索到从 i 到 i + n匹配的字符串,就继续搜索 i + n 到 s.length() 的字符串。但是深搜或者广搜存在大量的回溯,很容易超时。
- 官方解答给出了动态规划的版本,我在想,这也能动态规划?
确实能动态规划,每次我遇到搜索的题目,都想用深搜来暴搜,但是这很容易超时,我在想是不是自己的解题思路太单一了,然后这次看官方题解时,不仅仅是为了看懂这道题,而是如何把单一的思维扩展。
其实想了想,暴搜的话,我们也需要搜到字典里面的某个字符串和要匹配的字符串的某一段相同,那我们就可以继续搜索下一段的字符串,这其实就是状态转移(例如在dfs(idx),如果找到一段长度为n的字符串和要匹配的字符串某一段相同,就继续深搜dfs(idx + n))。
- 那其实都是在做状态转移,为什么动态规划不会超时,而深搜会超时呢?
其实大家都知道深搜会回溯,这是超时的根本原因。动态规划为什么不超时,这是因为动态规划没有回溯,而是使用动态规划数组(空间换时间),并不需要去回溯,只需要查询某个状态即可。
-
那么这里的动态转移方程是什么
dp[i] = dp[j] && true or false( include( s.substring(j, i) ) )
-
如果d[j]位置为真,表示在j位置,已经找到了能够从0到j位置匹配的字符串
-
true or false( include( s.substring(j, i) ) ),这里是表示,在所有字典里,有没有包含s从 j 到 i 的字符串。如果包含就返回true,反之false。
如果两个都为真,那就可以找到从0到 i 的组合,只要一直找到s.length(),那就可以知道能不能找到匹配的组合。
-
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> wordDictSet = new HashSet(wordDict);//转换为set,提高查找效率
boolean[] dp = new boolean[s.length() + 1];//动态转移数组
dp[0] = true;//空字符串一定为true
for(int i = 1; i <= s.length(); i++){//一直找到s.length()
for(int j = 0; j < i; j++){//每次都从0开始找,直到i - 1,这样会尽可能的把i以内的都匹配,其实这也把深搜回溯的功能完成了,但是代价比深搜回溯小
if(dp[j] && wordDictSet.contains(s.substring(j, i))){//动态转移方程
dp[i] = true;
break;
}
}
}
return dp[s.length()];
}
}