给你一个字符串 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
中的所有字符串 互不相同动态规划核心用处是求最值,当然也可用于判断是否存在可行性方案这类的问题。因为是先有多个可行性方案,才会衍生出最值问题。
故该问题也可以用动态规划,虽然题目只求截止到最后一个字母,字典中出现的单词是否能拼接出该字母及前面所组成的字符串 s。
动态规划的解决套路可分为 2 步,①基于问题能定义出状态,②状态间具备动态规划的三个特性
①基于问题定义出状态:问题是“求截止到最后一个字母所组成的字符串,是否可由字典拼接而成”。则状态就可以定义为“截止到某个字母所组成的字符串,是否可由字典拼接而成”。因为这样,当流转到最终状态时,即可得到问题的答案。
②状态间具备动态规划的三个特性
1、dp[i] 表示截止到第 i 个字符,所组成的字符串,是否可由字典拼接而成。
2、初始化,dp[0]表示空串,默认可由字典拼接而成
3、迭代处理截止到的每个字符所组成的字符串,判断字符串是否可由字典拼接而成
4、判断当前状态是否可达。即是否存在前面的某个状态,结合上本单词 word,转移到当前状态
5、若以当前字符为尾字符,在字典中找到了匹配字符串的后缀。再因为开头已经判断了结合该单词前的那个状态dp值为1,即前面状态可达,故当前状态可达。
public boolean wordBreak(String s, List<String> wordDict) {
// 1、dp[i] 表示截止到第 i 个字符,所组成的字符串,是否可由字典拼接而成。
int[] dp = new int[s.length() + 1];
// 2、初始化,dp[0]表示空串,默认可由字典拼接而成
dp[0] = 1;
for (int i = 0; i < s.length(); i++) {
// 3、迭代处理截止到的每个字符所组成的字符串,判断字符串是否可由字典拼接而成
int flag = 0;
for (String word : wordDict) {
// 4、判断当前状态是否可达。即是否存在前面的某个状态,结合上本单词 word,转移到当前状态
if (i - word.length() + 1 < 0 || dp[i - word.length() + 1] == 0) {
// 前面不存在某一状态 结合该单词 来达到当前状态。有以下2种场景
// 判断单词是否越界 || 加上本单词前,对应的上个状态还未可达
continue;
}
String sWord = s.substring(i - word.length() + 1, i + 1);
if (sWord.equals(word)) {
// 若以当前字符为尾字符,在字典中找到了匹配字符串的后缀。再因为开头已经判断了结合该单词前的那个状态dp值为1,即前面状态可达,故当前状态可达。
flag = 1;
}
dp[i + 1] = flag;
}
}
return dp[s.length()] == 1;
}
通用动态规划的解法,见标题二