← 返回题目列表

单词拆分如何用动态规划判断字符串能否由词典组成?

高频 中等 第 6 / 33 题 更新于 2026/07/30
动态规划字符串单词拆分前缀DP

简化版

单词拆分用 dp[i] 表示 s[0..i-1] 能否被词典拆分。枚举切分点 j,如果 dp[j] 为真且 s[j..i-1] 在词典中,则 dp[i]=true

详细版

这题的最后一步一定是某个词典单词作为后缀。设最后一个单词是 s[j:i],那么前缀 s[0:j] 必须也能被拆分,所以状态转移是 dp[i] = any(dp[j] && dict.contains(s.substring(j,i)))。初始化 dp[0]=true,表示空前缀可作为拼接起点。

优化点是限制后缀长度:如果词典最长单词长度为 maxLen,枚举 j 时只需要看 i - maxLeni 的范围。复杂度通常是 O(n^2) 次切分判断,具体还受 substring 成本影响。

完整版教学

一、把问题看成“最后一个单词是谁”

例如:

s = "leetcode"
dict = ["leet", "code"]

如果最后一个单词是 "code",那么前面的 "leet" 必须也能拆分。动态规划的关键就是枚举最后一个单词的起点。

leet | code
前缀可拆 + 后缀在词典

二、状态定义和初始化

定义 dp[i] 表示前 i 个字符 s[0..i-1] 是否能拆分。

状态含义
dp[0]空前缀可拆,作为起点
dp[4]s[0..3] 是否可拆
dp[n]整个字符串是否可拆

dp[0]=true 是必要的,因为当 s[0:i] 本身就是词典单词时,需要 dp[0] 支持它成立。

记忆钩子:前缀 DP 的空前缀通常是真,它代表“还没开始也算可连接”。

三、状态转移如何推导

如果 s[0:i] 能拆分,最后一个词一定是某段 s[j:i]。于是:

dp[i] = 存在 j,使得 dp[j] == true 且 s[j:i] 在词典中

这个转移把一个全局判断拆成“前缀已可拆”和“最后一段是合法单词”两个条件。

四、用样例走一遍

"leetcode" 为例:

i前缀可行切分dp[i]
0起点true
4leetdp[0] && "leet"true
8leetcodedp[4] && "code"true

如果输入是 "catsandog",词典有 "cats","dog","sand","and","cat",走到后缀 "og" 时没有合法拆分,最终 dp[n]=false

五、代码模板

boolean wordBreak(String s, List<String> wordDict) {
    Set<String> dict = new HashSet<>(wordDict);
    int maxLen = 0;
    for (String w : wordDict) maxLen = Math.max(maxLen, w.length());

    boolean[] dp = new boolean[s.length() + 1];
    dp[0] = true;
    for (int i = 1; i <= s.length(); i++) {
        for (int j = Math.max(0, i - maxLen); j < i; j++) {
            if (dp[j] && dict.contains(s.substring(j, i))) {
                dp[i] = true;
                break;
            }
        }
    }
    return dp[s.length()];
}

使用 HashSet 是为了让词典查询接近 O(1)。若语言的 substring 会复制字符,还要把 substring 成本也考虑进复杂度。

六、和回溯枚举所有句子的区别

题目目标状态返回
单词拆分 I是否存在拆分boolean
单词拆分 II返回所有拆分句子List<String>

单词拆分 I 可以用布尔 DP 提前终止;单词拆分 II 必须枚举所有答案,常用记忆化搜索缓存每个后缀的句子列表。

七、常见误区与追问

  • 误区:只要每个字符出现在某个单词中就可拆。 拆分要求连续子串组成词典单词,不是字符覆盖。
  • 误区:忘记 dp[0]=true 整个前缀本身是单词时会无法成立。
  • 误区:找到一个词典单词后不检查前缀。 后缀合法还不够,前缀也必须可拆。
  • 追问:为什么可以限制最大单词长度? 最后一个词长度不可能超过词典最长词。
  • 追问:复杂度是多少? 基础写法约 O(n^2) 次判断,substring 成本视语言实现而定。
  • 追问:如何返回具体路径? 用 DFS + memo 返回后缀所有句子,或者 DP 记录前驱切分点再回溯。

八、加强记忆

单词拆分要抓住“最后一个单词”这个视角:dp[i] 看前 i 个字符能否拆,枚举切点 j,只要 dp[j] 已经成立并且 s[j:i] 在词典中,当前前缀就成立。空前缀为真是拼接起点,HashSet 是查询优化,最大词长是循环剪枝。把它和单词拆分 II 的“返回所有句子”区分开,就能回答从存在性到枚举方案的追问。