给你一个字符串 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
>>思考和分析
本题是可以使用回溯算法进行暴搜的,就是去暴力搜索各种分割情况,然后去判断是否出现在这个字典里。但此文我们主要用完全背包来解决。
首先分析这道题目是让我们拆分这个单词,问是否出现在wordDict 字典里。那我们是不是可以反着想想,其实就是这个字典里边有这些单词,问我们能不能组成这个字符串s对吧。那这个字符串s可以理解成是一个容器或者是一个背包,那这些单词,就是一个物品。问题转化为 这些物品能不能正好装满这个背包,此时发现有点像一个背包类的问题。这里边的每一个物品是可以使用多次,例如说这个apple,所以说这就是一个完全背包类的问题。本题这些物品可以使用无限次,问我们能不能装满这个背包,求排列数。
s = applepenapple,wordDict = ["apple","pen"]
↓ ↓ ↓ ↓ ↓
-> 1 2 1 1 2
s这个字符串是 1 2 1这样的序列的,所以求的是排列数,我们需要先遍历背包,再遍历物品
- ① 每一个物品是可以使用多次(完全背包问题)
- ② 由于所求为排列数,确定遍历顺序
>>动规五部曲
1.确定dp数组以及下标的含义
dp[i]:字符串长度为 i 的话,dp[i] 为 true,表示可以拆分为一个或多个在字典中出现的单词。
2.确定递推公式
如果确定dp[j] 是 true,且 [j,i] 这个区间的子串出现在字典里,那么 dp[i] 一定是 true。(j<i)
所以递推公式是
if([j,i] 这个区间的子串出现在字典里 && dp[j] == true)
dp[i] = true
3.dp 数组初始化
- 从递推公式中可以看出,dp[i] 的状态依靠 dp[j] 是否为 true。那么dp[0] 则是递推的根基,dp[0]一定要初始化为true,否则递归下去后面全是false了。
- 下标非0的dp[i]初始化为false,只要没有被覆盖说明都是不可拆分为一个或多个在字典中出现的单词
思考:dp[0] 有没有意义?
dp[0] 表示如果字符串为空的话,说明出现在字典里,但题目中说了“给定一个非空字符串 s” 所以测试数据中不会出现i为0的情况,那么dp[0]初始为true完全就是为了推导公式。
4.确定遍历顺序
本题一定是先遍历背包,再遍历物品。原因如下:
在完全背包中:
- 如果求组合数就是外层for循环遍历物品,内层for遍历背包
- 如果求排列数就是外层for遍历背包,内层for循环遍历物品
s = "applepenapple", wordDict = ["apple", "pen"] 中,"apple", "pen" 是物品,那么要求 物品的组合一定是 "apple" + "pen" + "apple" 才能组成 "applepenapple"。"apple" + "apple" + "pen" 或者 "pen" + "apple" + "apple" 是不可以的,那么就是强调物品之间顺序。
5.举例推荐dp[i]
s = "leetcode",wordDict = ["leet","coded"],dp状态如图:
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 word = s.substr(j, i - j); //substr(起始位置,截取的个数)
if (wordSet.find(word) != wordSet.end() && dp[j]) {
dp[i] = true;
}
}
}
return dp[s.size()];
}
};
- 时间复杂度:O(n^3),因为substr返回子串的副本是O(n)的复杂度(这里的n是substring的长度)
- 空间复杂度:O(n)
参考和推荐文章、视频:
代码随想录 (programmercarl.com)
动态规划之完全背包,你的背包如何装满?| LeetCode:139.单词拆分_哔哩哔哩_bilibili
来自代码随想录的课堂截图: