LeetCode 第 139 题「单词拆分」
字数 1308 2025-10-25 23:35:25
好的,我们来看 LeetCode 第 139 题「单词拆分」。
题目描述
给定一个字符串 s 和一个字符串列表 wordDict 作为字典。请判断是否可以利用字典中出现的单词拼接出 s。
注意:
- 不要求字典中出现的单词全部都使用。
- 字典中的单词可以重复使用。
示例 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
解题思路
这个问题可以用 动态规划 来解决。
第一步:定义状态
我们定义 dp[i] 表示字符串 s 的前 i 个字符(即 s[0..i-1])能否被字典中的单词拼接而成。
- 例如
dp[4]表示s[0..3]这个子串能否被拆分。 - 最终目标是求
dp[n],其中n是s的长度。
第二步:状态转移
为了计算 dp[i],我们考虑前 i 个字符的最后一个单词可能是哪些:
- 我们枚举一个位置
j(0 <= j < i),将字符串分成两部分:s[0..j-1]对应dp[j]:表示这部分能否被拆分。s[j..i-1]对应子串substr(j, i-j):检查它是否在字典中。
- 如果存在某个
j,使得dp[j]为true,并且s[j..i-1]在字典中,那么dp[i]就为true。
第三步:初始条件
dp[0] = true,表示空字符串可以被拆分(即不选任何单词即可组成空串)。
第四步:计算顺序
从小到大计算 dp[i],i 从 1 到 n。
详细步骤示例
以 s = "leetcode", wordDict = ["leet", "code"] 为例:
-
初始化:
dp[0] = true,其余为false。
-
i = 1:"l"不在字典中,dp[1] = false。 -
i = 2:"le"不在字典中,dp[2] = false。 -
i = 3:"lee"不在字典中,dp[3] = false。 -
i = 4:j = 0:dp[0]为true,检查s[0..3] = "leet"在字典中 →dp[4] = true。
-
i = 5~i = 7:没有单词匹配,保持false。 -
i = 8:j = 4:dp[4]为true,检查s[4..7] = "code"在字典中 →dp[8] = true。
最终 dp[8] 为 true,返回 true。
复杂度分析
- 时间复杂度:O(n²),其中
n是字符串长度。对每个i,需要枚举j从0到i-1。 - 空间复杂度:O(n) 用于
dp数组,以及将wordDict转为哈希集合 O(m)(m 为字典中单词总长度)。
代码实现(思路)
def wordBreak(s, wordDict):
word_set = set(wordDict)
n = len(s)
dp = [False] * (n + 1)
dp[0] = True
for i in range(1, n + 1):
for j in range(i):
if dp[j] and s[j:i] in word_set:
dp[i] = True
break
return dp[n]
总结
这个问题的核心是:
- 将大问题分解为子问题:前
i个字符能否拆分。 - 利用字典查找快速判断最后一个单词是否合法。
- 通过动态规划避免重复计算。
这样就能高效判断字符串是否能被字典单词拆分。