要解决这个问题,可以使用动态规划的思路。具体来说,我们定义一个布尔数组 `dp`,其中 `dpi` 表示字符串 `s` 的前 `i` 个字符是否可以通过字典中的单词拼接而成。以下是详细的实现思路和代码:
---
解题思路
1. 动态规划定义
用 `dpi` 表示 `s` 的前 `i` 个字符(即 `s0..i-1`)能否被字典中的单词拼接。初始时,`dp0 = true`(空字符串默认合法)。
2. 状态转移
对于每个位置 `i`,遍历所有可能的分割点 `j`(`0 ≤ j < i`)。若 `dpj` 为 `true`(即前 `j` 个字符已合法),且子串 `sj..i-1` 存在于字典中,则 `dpi` 设为 `true`。
3. 优化查询效率
将字典转换为哈希集合 `unordered_set`,使单词查询的时间复杂度降为 `O(1)`。
4. 遍历顺序
外层遍历 `i` 从 `1` 到 `s.length()`,内层遍历 `j` 从 `0` 到 `i-1`。当找到合法的分割点时,可提前终止内层循环。
---
代码实现
cpp
include <vector>
include <unordered_set>
include <string>
using namespace std;
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> dict(wordDict.begin(), wordDict.end());
int n = s.size();
vector<bool> dp(n + 1, false);
dp0 = true; // 空字符串初始为true
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < i; ++j) {
if (dpj && dict.count(s.substr(j, i - j))) {
dpi = true;
break; // 找到一个合法分割即可终止内层循环
}
}
}
return dpn;
}
};
---
关键点分析
1. 动态规划数组初始化
空字符串的合法性是递推的基础,因此 `dp0` 必须初始化为 `true`。
2. 子串查询优化
使用 `unordered_set` 存储字典,查询时间复杂度为 `O(1)`,显著提升效率。
3. 提前终止内层循环
一旦找到合法的分割点 `j`,即可标记 `dpi` 为 `true` 并跳出内层循环,减少不必要的计算。
---
复杂度分析
- 时间复杂度:`O(n^2)`,其中 `n` 是字符串 `s` 的长度。两层循环最坏情况下需要遍历所有可能的分割点。
- 空间复杂度:`O(n + m)`,`n` 是 `dp` 数组的大小,`m` 是字典中单词的数量(哈希集合的空间开销)。
---
示例验证
以输入 `s = "leetcode", wordDict = "leet", "code"` 为例:
1. `i=4` 时,`j=0`,子串 `"leet"` 在字典中,`dp4` 设为 `true`。
2. `i=8` 时,`j=4`,子串 `"code"` 在字典中,`dp8` 设为 `true`,最终返回 `true`。
此方法通过动态规划高效地解决了字符串拼接问题,适用于字典单词可重复使用的情况。