【Leetcode】139. 单词拆分

60 阅读1分钟

leetcode-139.png

动态规划(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)

拓展:记录拆分路径,输出组成的单词

如果想要记录里面的构成的单词,可以记录路径,也就是上面的ij

我们不仅想知道 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;
};

result.png