一、题目
给定一个非空字符串s和一个包含非空单词的列表wordDict,判定s是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
1.
拆分时可以重复使用字典中的单词。
2.
你可以假设字典中没有重复的单词。
示例1:
示例2:
示例3:
二、求解思路
这道题目要求我们将字符串 `s` 分割成若干子串,并验证这些子串是否全部包含在给定的字典 `wordDict` 中。
我们定义 `dp[i]` 表示字符串 `s` 的前 `i` 个字符是否可以被成功拆分,并且所有拆分出的子串都存在于字典 `wordDict` 中。为了求解 `dp[i]`,我们需要考虑从第 `i` 个字符向前截取 `k` 个字符,检查子串 `[i-k+1, i]` 是否在字典 `wordDict` 中,同时确保前面的子串 `[0, i-k]` 也能被正确拆分且其子串也都存在于字典中。
具体来说,我们需要遍历字符串 `s` 的每个位置 `i`,并尝试从当前位置向前截取不同长度的子串,验证这些子串是否在字典中,并且之前的部分是否也能被正确拆分。通过这种方式,我们可以逐步构建出 `dp` 数组,最终判断整个字符串 `s` 是否可以被成功拆分。如下图所示
三、代码实现
前面[0,i -k]子串拆分的子串是否都存在于 wordDict 中只需要判断 dp[i -k] 即可,而子串 [i -k+1,i]是否存在于字典 wordDict 中需要查找,所以动态规划的递推公式很容易列出来
dp[i] = dp[i - k] && (wordDict.find(s.substr(i - k, k)) != wordDict.end());
这个k我们需要一个个枚举,我们来看下最终代码
#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>
bool wordBreak(const std::string& s, const std::vector<std::string>& dict) {
std::unordered_set<std::string> wordDict(dict.begin(), dict.end());
std::vector<bool> dp(s.length() + 1, false);
dp[0] = true; // 空字符串总是可以被拆分的
for (int i = 1; i <= s.length(); i++) {
// 枚举k的值
for (int k = 0; k <= i; k++) {
// 如果往前截取全部字符串,我们直接判断子串[0,i-1]
// 是否存在于字典wordDict中即可
if (k == i) {
if (wordDict.find(s.substr(0, i)) != wordDict.end()) {
dp[i] = true;
continue;
}
}
// 递推公式
dp[i] = dp[i - k] && (wordDict.find(s.substr(i - k, k)) != wordDict.end());
// 如果dp[i]为true,说明前i个字符串结果拆解可以让他的所有子串
// 都存在于字典wordDict中,直接终止内层循环,不用再计算dp[i]了。
if (dp[i]) {
break;
}
}
}
return dp[s.length()];
}
int main() {
std::string s = "leetcode";
std::vector<std::string> dict = {"leet", "code"};
bool result = wordBreak(s, dict);
std::cout << std::boolalpha << result << std::endl; // 输出: true
return 0;
}
上面代码有一个判断,就是截取的是前面全部字符串的时候要单独判断,其实当截取全部
的 时 候 我 们 只 需 要 判 断 这 个 字 符 串 是 否 存 在 于 字 典 wordDict 中 即 可 , 可 以 让 dp[0] 为 true,dp[0]表示的是空字符串。这样代码会简洁很多,我们来看下
#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>
bool wordBreak(const std::string& s, const std::vector<std::string>& dict) {
std::unordered_set<std::string> wordDict(dict.begin(), dict.end());
std::vector<bool> dp(s.length() + 1, false);
dp[0] = true; // 边界条件
for (int i = 1; i <= s.length(); i++) {
for (int j = 0; j < i; j++) {
dp[i] = dp[j] && (wordDict.find(s.substr(j, i - j)) != wordDict.end());
if (dp[i]) {
break;
}
}
}
return dp[s.length()];
}
int main() {
std::string s = "leetcode";
std::vector<std::string> dict = {"leet", "code"};
bool result = wordBreak(s, dict);
std::cout << std::boolalpha << result << std::endl; // 输出: true
return 0;
}
这个和第一种写法不太一样,这个每次截取的方式如下图所示。
这道题目可以被视为一个完全背包问题,其中背包是字符串 `s`,而可供选择的商品则是字典中的字符串。由于字典中的字符串可以被无限次选择且没有数量限制,因此这个问题符合完全背包问题的特征。完全背包问题将在后续的讲解中详细探讨。