题目链接:
给你一个字符串 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
中的所有字符串 互不相同
解题思路
题目中说明字典中的单词可以重复使用,并且目的是能不能拼成给定的字符串,所以这是一个完全背包问题。
wordDict就是物品,字符串s就是背包容量,题目问单词能否拼成字符串s,其实就是物品能不能把背包装满。
1、定义dp数组
dp[i] : 字符串s的前i个字符组成的字符串s[0,i-1]能否被拆分成若干个字典中出现的单词。
2、确定状态转移方程
dp[i]之前就下面这些状态: dp[i-1],dp[i-2],...,dp[i-k],... ,但是与哪个状态有关?
dp[i]只与dp[i-k] ,记为dp[j]有关系,其中k为s[i:j]的长度。
我们假设: s[0:i] 字串对应dp[i+1],它是否为true取决于两点:
它的前缀字串s[0:j-1]的dp[j],是否为true 剩余字串s[j:i],是否在单词列表里。 3、边界条件 如果字符串为空的话,说明出现在字典里。虽然看似荒谬,但是这只是为了让边界的情况也能套用状态转移方程而已。 dp[0] = true;
4、确定dp数组的计算顺序
一维dp数组解决完全背包问题,求组合问题先正序遍历物品,再正序遍历背包。求排列问题先正序遍历背包,再正序遍历物品。针对本题采用后者更容易写。
C++
class Solution { public: bool wordBreak(string s, vector<string>& wordDict) { unordered_set<string> wordSet(wordDict.begin(), wordDict.end()); vector<bool> dp(s.size() + 1, false); dp[0] = true; for (int i = 1; i <= s.size(); i++) { for (int j = 0; j < i; j++) { string s1 = s.substr(j, i - j); if (dp[j] && (wordSet.find(s1) != wordSet.end())) { dp[i] = true; } } } return dp[s.size()]; } };
/** * @param {string} s * @param {string[]} wordDict * @return {boolean} */ var wordBreak = function(s, wordDict) { const dp = new Array(s.length + 1).fill(false); dp[0] = true; for (let i = 1; i <= s.length; i++) { for (let j = 0; j < i; j++) { let s1 = s.slice(j, i); if (dp[j] && wordDict.find(obj => obj == s1)) { dp[i] = true; } } } return dp[s.length]; };