☰
动态规划解决力扣Word Break问题:算法与优化
2026/10/26 10:59:17 网站建设 项目流程

1. 项目概述:Word Break问题与力扣算法解析

Word Break(单词拆分)是力扣平台上经典的动态规划题型,编号为第139题。这道题要求判断给定字符串能否被拆分成字典中的单词组合,考察的是对字符串处理、边界条件分析和动态规划状态转移的综合应用能力。

我在刷题过程中发现,很多初学者容易陷入暴力递归的陷阱,或者无法准确构建状态转移方程。本文将分享一套经过实战检验的解题框架,包含Python和Java两种实现,并重点解析如何将问题转化为标准的动态规划模型。对于正在准备算法面试的开发者,掌握这类分割型问题的解题模式能显著提升笔试通过率。

2. 核心算法原理与解题思路

2.1 问题重述与示例分析

给定一个非空字符串s和一个包含非空单词列表的字典wordDict,判断s是否可以被分割成一个或多个字典单词的空格分隔序列。例如:

  • 输入:s = "leetcode", wordDict = ["leet", "code"]
  • 输出:true("leetcode"可拆分为"leet code")

2.2 动态规划解法设计

正确的解法应采用动态规划,其核心在于:

  1. 定义dp数组:dp[i]表示字符串s的前i个字符能否被拆分
  2. 初始化:dp[0] = true(空字符串视为可拆分)
  3. 状态转移方程:
    for j in range(i): if dp[j] and s[j:i] in wordDict: dp[i] = True break
  4. 最终结果:dp[n](n为字符串长度)

2.3 时间复杂度优化

将wordDict转换为集合(set)可将查找操作降至O(1):

wordSet = set(wordDict) max_len = max(len(word) for word in wordSet) if wordSet else 0

加入最大长度剪枝:

for j in range(i-1, max(i-max_len-1, -1), -1): if dp[j] and s[j:i] in wordSet: dp[i] = True break

这样可将时间复杂度从O(n³)优化至O(n²)

3. 完整代码实现与逐行解析

3.1 Python版本实现

def wordBreak(s, wordDict): wordSet = set(wordDict) max_len = max(len(word) for word in wordSet) if wordSet else 0 n = len(s) dp = [False] * (n + 1) dp[0] = True for i in range(1, n + 1): start = max(0, i - max_len - 1) for j in range(start, i): if dp[j] and s[j:i] in wordSet: dp[i] = True break return dp[n]

关键点说明:

  1. 第2行:转换为集合提升查找效率
  2. 第3行:记录字典中最长单词长度
  3. 第6行:dp数组初始化
  4. 第9行:通过max_len限制内层循环范围
  5. 第11行:核心状态转移判断

3.2 Java版本实现

public boolean wordBreak(String s, List<String> wordDict) { Set<String> wordSet = new HashSet<>(wordDict); int maxLen = wordSet.stream().mapToInt(String::length).max().orElse(0); boolean[] dp = new boolean[s.length() + 1]; dp[0] = true; for (int i = 1; i <= s.length(); i++) { int start = Math.max(0, i - maxLen - 1); for (int j = start; j < i; j++) { if (dp[j] && wordSet.contains(s.substring(j, i))) { dp[i] = true; break; } } } return dp[s.length()]; }

4. 常见错误分析与调试技巧

4.1 典型错误案例

  1. 直接使用递归导致超时:

    # 错误示范(指数级时间复杂度) def wordBreak(s, wordDict): if not s: return True return any(s.startswith(word) and wordBreak(s[len(word):], wordDict) for word in wordDict)
  2. 忘记处理空字符串初始化:

    // 错误示范:未初始化dp[0] boolean[] dp = new boolean[s.length() + 1]; // 直接开始循环会导致错误

4.2 调试建议

  1. 打印dp数组观察状态变化:

    print(f"i={i}, dp={dp}")
  2. 边界条件测试用例:

    • 空字符串
    • 字典为空
    • 字符串包含字典中没有的字符
    • 字典中有空字符串
  3. 性能测试用例:

    s = "a"*100 + "b" wordDict = ["a"] + ["a"*i for i in range(1, 10)]

5. 算法扩展与变种问题

5.1 Word Break II(输出所有可能分割)

在判断能否分割的基础上,需要记录所有可能的分割方式。此时需要:

  1. 使用记忆化递归存储中间结果
  2. 回溯法构建所有路径

5.2 前缀树(Trie)优化

当字典规模极大时,可用前缀树替代集合:

class TrieNode: def __init__(self): self.children = {} self.is_word = False def build_trie(wordDict): root = TrieNode() for word in wordDict: node = root for c in word: node = node.children.setdefault(c, TrieNode()) node.is_word = True return root

5.3 实际应用场景

  1. 文本断词处理
  2. 代码语法检查
  3. 生物信息学的序列分析
  4. 密码破解中的字典攻击

6. 刷题经验与面试技巧

  1. 识别动态规划问题的特征:

    • 求"是否可行"、"最值"、"总数"类问题
    • 问题可以分解为子问题
    • 子问题之间存在重叠
  2. 面试应答策略:

    • 先说明暴力解法及其缺陷
    • 引出动态规划优化思路
    • 讨论时间/空间复杂度
    • 提出可能的优化方向
  3. 力扣刷题路线建议:

    • 先掌握基础动态规划(斐波那契、爬楼梯)
    • 再练习线性DP(最大子数组和、买卖股票)
    • 最后攻克字符串DP(编辑距离、回文分割)

提示:在面试中遇到变种问题时,可以先询问面试官是否允许使用字典预处理等优化手段,这既能展示沟通能力,也体现工程思维。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询