1. 问题背景与核心挑战
第一次看到力扣139题"单词拆分"时,我正啃着面包刷每日一题。题目描述很简单:给定一个非空字符串s和一个包含非空单词列表的字典wordDict,判断s是否能被拆分成字典中的单词序列。比如:
code复制s = "leetcode", wordDict = ["leet", "code"]
输出true,因为"leetcode"可以拆分为"leet"+"code"。
但实际处理时发现几个关键难点:
- 字典中的单词可以重复使用
- 拆分时不需要使用所有字典单词
- 字符串可能长达1000字符,暴力搜索会超时
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动态规划解法详解
2.1 状态定义与转移方程
经过多次尝试,我发现动态规划(DP)是最优解。定义dp[i]表示字符串s的前i个字符能否被拆分:
python复制dp = [False] * (len(s) + 1)
dp[0] = True # 空字符串默认可拆分
状态转移方程:
python复制for i in range(1, len(s)+1):
for j in range(i):
if dp[j] and s[j:i] in wordDict:
dp[i] = True
break
2.2 时间复杂度优化
原始解法时间复杂度O(n³),通过两个优化可以降到O(n²):
- 预处理字典为哈希集合,查询时间O(1)
- 限制单词最大长度,内层循环只需检查max_len长度
优化后的核心代码:
python复制max_len = max(len(word) for word in wordDict) if wordDict else 0
word_set = set(wordDict)
for i in range(1, len(s)+1):
start = max(0, i - max_len)
for j in range(start, i):
if dp[j] and s[j:i] in word_set:
dp[i] = True
break
3. 边界条件与特殊测试用例
3.1 必须处理的边界情况
- 空字符串:应返回True
- 字典为空:除非s也是空字符串,否则False
- 字典包含空字符串:需特殊处理
3.2 典型测试用例
python复制测试用例1:
s = "catsandog", wordDict = ["cats","dog","sand","and","cat"]
输出:False
测试用例2:
s = "applepenapple", wordDict = ["apple","pen"]
输出:True
测试用例3:
s = "a"*1000, wordDict = ["a"]
输出:True
4. 记忆化搜索解法对比
4.1 递归+备忘录实现
虽然DP更优,但递归解法更容易理解:
python复制def wordBreak(s, wordDict):
memo = {}
def dfs(s):
if not s: return True
if s in memo: return memo[s]
for word in wordDict:
if s.startswith(word):
if dfs(s[len(word):]):
memo[s] = True
return True
memo[s] = False
return False
return dfs(s)
4.2 性能对比
| 方法 | 时间复杂度 | 空间复杂度 | LeetCode执行时间 |
|---|---|---|---|
| 原始DP | O(n³) | O(n) | 120ms |
| 优化DP | O(n²) | O(n) | 45ms |
| 记忆化搜索 | O(n²) | O(n) | 60ms |
5. 实际编码中的坑点
- 字典预处理:忘记将wordDict转为集合会导致O(n)查询时间
- 索引处理:dp数组长度应为len(s)+1,容易漏掉dp[0]
- 子串切片:Python的s[j:i]不包含s[i],与dp[i]表示前i个字符要对应
- 空字典处理:wordDict为空时应直接返回False(除非s也是空)
关键提示:在面试中建议先写暴力解法,再优化为DP,展示思考过程比直接写最优解更重要
6. 同类问题拓展
掌握单词拆分后,可以解决这些变种题:
- 140.单词拆分II(输出所有可能拆分)
- 472.连接词(找出可由其他单词组成的单词)
- 1391.检查网格中是否存在有效路径(类似DP思想)
对于140题,需要在DP基础上记录路径,再用DFS回溯所有解:
python复制def wordBreak(s, wordDict):
word_set = set(wordDict)
dp = [[] for _ in range(len(s)+1)]
dp[0] = [""]
for i in range(1, len(s)+1):
for j in range(i):
if dp[j] and s[j:i] in word_set:
for seq in dp[j]:
dp[i].append(seq + (" " if seq else "") + s[j:i])
return dp[-1]
7. 刷题经验分享
- 调试技巧:打印dp数组中间状态,观察何时变为True
- 测试策略:先测试短字符串,再测长重复字符(如"aaaaa")
- 优化路线:暴力→记忆化→DP→DP优化是通用解题路径
- 空间优化:有些DP问题可以用滚动数组,但这题不行
我曾在周赛遇到类似题目,因为没有限制max_len导致TLE。后来发现测试用例中有超长重复子串,加上max_len优化后顺利AC。这个教训让我明白:看似简单的题目,边界条件才是真正考点。
