动态规划(DP)
状态定义:
用 dp[i] 表示字符串 s 的前 i 个字符(s[0..i-1])是否可以被字典中的单词拼接而成。
状态转移方程:
对于每个位置 i,我们尝试遍历所有前面的 j,判断 dp[j] 是否为 true 且 s[j:i] 是否在字典中:
dp[i] = true ⟺ 存在 j ∈ [0, i),使得 dp[j] 为 true 且 s[j:i] ∈ wordDict
初始状态:
dp[0] = true // 空字符串可以被“拼出”
dp
这里的内层循环就遍历了所有已经存在的单词
var wordBreak = function (s, wordDict) {
let wordSet = new Set(wordDict);
let n = s.length;
let dp = new Array(n + 1).fill(false);
dp[0] = true;
for (let i = 1; i <= n; ++i) {
for (let j = 0; j < i; ++j) {
if (dp[j] && wordSet.has(s.slice(j, i))) {
dp[i] = true;
}
}
}
return dp[n];
};
- 时间复杂度:O(n²)
- 空间复杂度:O(n)
拓展:记录拆分路径,输出组成的单词
如果想要记录里面的构成的单词,可以记录路径,也就是上面的i和j
我们不仅想知道 s 是否可以被拼接,还想知道 具体是由哪些单词拼接而成的。
实现方法:
- 使用一个
path数组来记录每个i是从哪个j转移过来的。 - 如果
dp[n]为true,就从后往前回溯路径,还原构成的单词列表。
/**
* @param {string} s
* @param {string[]} wordDict
* @return {boolean}
*/
var wordBreak = function (s, wordDict) {
let wordSet = new Set(wordDict);
let n = s.length;
let dp = new Array(n + 1).fill(false);
let path = new Array(n + 1).fill(null);
dp[0] = true;
for (let i = 1; i <= n; ++i) {
for (let j = 0; j < i; ++j) {
if (dp[j] && wordSet.has(s.slice(j, i))) {
dp[i] = true;
path[i] = j;
break;
}
}
}
if (!dp[n]) return null;
const words = [];
let i = n;
while (i > 0) {
const j = path[i];
words.push(s.slice(j, i));
i = j;
}
console.log(words)
return words;
};