跳到正文
第 139 题中等

单词拆分

Word Break

给定字符串和单词字典,判断能否把字符串切成若干个字典中的单词(单词可重复使用)。

在力扣中练习

题目描述

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。

注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

 

示例 1:

输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。

示例 2:

输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。
     注意,你可以重复使用字典中的单词。

示例 3:

输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false

 

提示:

  • 1 <= s.length <= 300
  • 1 <= wordDict.length <= 1000
  • 1 <= wordDict[i].length <= 20
  • s 和 wordDict[i] 仅由小写英文字母组成
  • wordDict 中的所有字符串 互不相同

解题思路

主解法

一维 DP · 枚举切分点

dp[i] 只关心前 i 个字符能不能拆完

暴力做法是枚举所有切分位置:n 个字符之间有 n-1 个空隙,每个都可以切或不切,共 2^(n-1) 种,指数级。

但判断“前 i 个字符能不能拆完”并不需要记住具体怎么拆。只要存在一个切分点 j,使得前 j 个字符能拆完,并且 s[j..i) 正好是字典里的单词,那么前 i 个字符就能拆完:

dp[i] = 存在 j < i:dp[j] 为真 且 s[j..i) 在字典中

自拟主例 s = "abcabc",字典含 a / ab / abc / bc。前 3 个字符既可由 abc 拆出,也可由 a + bc 得到;到第 6 个字符时,abc 接在 3 后面、bc 接在 4 后面,两条路都通。

分步图解

前 1 个字符:"a" 都能接到一个可切分的前缀,dp[1] = T。

i
1
dp[i]
T
单词拆分 · 切分点台账写入答案

dp[i] 表示 s 的前 i 个字符能否被拆成字典中的单词;s = "abcabc"

T初dp[0]:T,最优候选的来源Tdp[1]:T,当前目标·dp[2]:尚未计算·dp[3]:尚未计算·dp[4]:尚未计算·dp[5]:尚未计算·dp[6]:尚未计算0123456
来源子问题候选状态
"a"dp[0] = TT✓

写入T

dp[1]=dp[0] && "a"
=T && true=T
未计算初值
1 / 3
a、ab、abc 都能切出来;到第 3 个字符时 abc 和 bc 两个来源同时成立。

dp[0] = true 是空串的初值,也是“从开头切第一个单词”的入口。按 i 从小到大枚举,所有 dp[j](j < i)都已经确定,不需要回溯具体的切分方案。

分步图解

前 5 个字符:"ab" 都能接到一个可切分的前缀,dp[5] = T。

i
5
dp[i]
T
单词拆分 · 切分点台账写入答案

dp[i] 表示 s 的前 i 个字符能否被拆成字典中的单词;s = "abcabc"

T初dp[0]:TTdp[1]:TTdp[2]:TTdp[3]:T,最优候选的来源Tdp[4]:TTdp[5]:T,当前目标·dp[6]:尚未计算0123456
来源子问题候选状态
"ab"dp[3] = TT✓

写入T

dp[5]=dp[3] && "ab"
=T && true=T
未计算初值
1 / 3
第 6 个字符前有 abc 和 bc 两个来源,任意一个成立就够。

时间复杂度 O(n²·L)(L 为单词长度,主要花在取子串和哈希),空间 O(n)。

时间 O(n²·L)空间 O(n)

易错点

  • dp[0] 设为 false:所有从开头切出的第一个单词都无法成立,整张表全假。
  • 子串区间写成 s.substr(j, i) 而不是 s.substr(j, i - j),长度多算了。
  • 只判断“最后一个单词在字典里”而忘了前面的前缀也要可拆,dp[j] 的条件不能省。
对照解法

BFS · 可达切分点

从下标 0 出发,能切出的每个结尾都是一个新节点

时间同为 O(n²·L),BFS 把切分点当成图上的点,适合只问“能不能到达”;主解法 DP 更短,也不依赖队列。

把字符串的每个下标看成一个点:如果从 start 能切出一个字典单词到 end,就连一条 start → end 的边。问题变成“0 能否走到 n”。

BFS 从 0 开始,每次尝试所有可能的结尾 end,把能切出的 end 入队。每个点只入队一次,第一次到达 n 就可以返回 true。

它和 DP 的区别只是推进方式:DP 按 i 递增检查所有 j,BFS 按可达顺序扩展。空间都是 O(n),BFS 还多一个队列。

  1. 1seen[0] = true,0 入队。
  2. 2弹出 start,若 start == n 返回 true。
  3. 3枚举 end,若 s[start..end) 在字典且未访问,标记并入队。
  4. 4队列空仍未到 n,返回 false。
时间 O(n²·L)空间 O(n)

易错点

  • 在出队时才标记 visited:同一个 end 可能被多个 start 重复入队。
  • 字典里出现空串:end 从 start+1 开始,避免原地打转。

代码与演示

主解法配有图解;对照解法用于比较思路、复杂度或实现方式。

演示输入s = "abcabc", wordDict = ["a","ab","abc","bc"]

dp[0] = T:空串不需要切分,是唯一初值。

s
abcabc
单词拆分 · 切分点台账建立初值

dp[i] 表示 s 的前 i 个字符能否被拆成字典中的单词;s = "abcabc"

T初dp[0]:T,当前目标·dp[1]:尚未计算·dp[2]:尚未计算·dp[3]:尚未计算·dp[4]:尚未计算·dp[5]:尚未计算·dp[6]:尚未计算0123456
来源子问题候选状态
初值,无需候选
dp[0]=T
未计算初值
1 / 8
查看 C++ 代码

延伸阅读

  • 如果要求输出所有切分方案(140. 单词拆分 II),就不能只存布尔值,需要记录每个位置的前驱并做回溯。
  • 字典用哈希集合而不是线性查找,是这类题能把 O(n²·L) 控制住的关键。