Java版LeetCode热题100之单词拆分:从动态规划到面试实战的全面解析

Java版LeetCode热题100之单词拆分:从动态规划到面试实战的全面解析

本文深入剖析 LeetCode 第139题「单词拆分」,涵盖题目理解、算法设计、代码实现、复杂度分析、优化思路、数据结构基础、面试应对策略以及实际应用场景等多个维度,是一篇面向中高级开发者的高质量技术博客。


一、原题回顾

题目编号:LeetCode 139
题目名称:单词拆分(Word Break)
难度等级:中等(Medium)

题目描述

给你一个字符串s和一个字符串列表wordDict作为字典。请你判断是否可以利用字典中出现的一个或多个单词拼接出s

  • 不要求字典中的所有单词都被使用;
  • 字典中的单词可以重复使用
  • 所有字符串仅包含小写英文字母;
  • 字典中所有单词互不相同。

示例

示例 1: 输入: s = "leetcode", wordDict = ["leet", "code"] 输出: true 解释: "leetcode" 可由 "leet" + "code" 拼接而成。 示例 2: 输入: s = "applepenapple", wordDict = ["apple", "pen"] 输出: 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
  • 所有字符串仅由小写字母组成

二、原题分析

这道题的核心在于:能否将一个长字符串完全拆分成若干个字典中存在的子串

乍看之下,可能想到暴力回溯(DFS)——尝试从头开始匹配每一个可能的单词,若匹配成功则递归处理剩余部分。但这种做法在最坏情况下时间复杂度极高(指数级),尤其当字符串较长且字典较大时,极易超时。

因此,我们需要一种更高效的策略。观察问题结构:

  • 如果我们知道s[0..i-1]能否被拆分,那么对于s[0..j-1](其中j > i),只需检查s[i..j-1]是否在字典中即可。
  • 这种“当前状态依赖于之前状态”的特性,正是动态规划(Dynamic Programming, DP)的典型应用场景。

此外,题目允许单词重复使用,说明这是一个完全背包类问题的变种——每个“物品”(字典单词)可无限次使用,目标是“恰好装满”整个字符串。


三、答案构思:动态规划解法

3.1 状态定义

我们定义一个布尔数组dp,其中:

  • dp[i]表示字符串s的前i个字符(即s[0..i-1])是否可以被字典中的单词完全拆分。

注意:dp[0]对应空字符串,我们约定dp[0] = true,作为空状态的合法起点。

3.2 状态转移方程

对于每个位置i(从 1 到nn = s.length()),我们尝试所有可能的分割点j0 <= j < i):

  • 如果dp[j] == true(即前j个字符可拆分)
  • 且子串s.substring(j, i)存在于字典中

那么dp[i] = true

形式化表达为:

d p [ i ] = ⋁ j = 0 i − 1 ( d p [ j ] ∧ ( s [ j . . i − 1 ] ∈ wordDict ) ) dp[i] = \bigvee_{j=0}^{i-1} \left( dp[j] \land (s[j..i-1] \in \text{wordDict}) \right)dp[i]=j=0i1(dp[j](s[j..i1]wordDict))

其中⋁ \bigvee表示逻辑“或”操作。

3.3 字典查询优化

为了快速判断子串是否在字典中,我们将wordDict转换为HashSet<String>,使得每次contains()操作的时间复杂度为O(1)(平均情况)。


四、完整答案(Java实现)

importjava.util.*;publicclassSolution{publicbooleanwordBreak(Strings,List<String>wordDict){// 将字典转为 HashSet,提升查找效率Set<String>wordSet=newHashSet<>(wordDict);intn=s.length();// dp[i] 表示 s 的前 i 个字符能否被拆分boolean[]dp=newboolean[n+1];dp[0]=true;// 空字符串视为可拆分// 遍历每个位置 i(1 到 n)for(inti=1;i<=n;i++){// 尝试所有可能的分割点 j(0 到 i-1)for(intj=0;j<i;j++){// 如果前 j 个字符可拆分,且 s[j..i-1] 在字典中if(dp[j]&&wordSet.contains(s.substring(j,i))){dp[i]=true;break;// 找到一个合法分割即可,无需继续}}}returndp[n];}}

代码说明

  • 使用HashSet存储字典,确保 O(1) 查找;
  • 外层循环遍历字符串长度i
  • 内层循环枚举分割点j
  • 一旦找到合法组合,立即break,避免无效计算;
  • 最终返回dp[n],即整个字符串是否可拆分。

五、代码分析

5.1 正确性验证

s = "leetcode",wordDict = ["leet", "code"]为例:

  • dp[0] = true
  • i=4时,j=0s[0..3]="leet"∈ dict →dp[4]=true
  • i=8时,j=4dp[4]=trues[4..7]="code"∈ dict →dp[8]=true
  • 返回dp[8] = true

再看反例s = "catsandog"

  • 虽然"cat""cats"都存在,但后续"sandog"无法被拆分;
  • 所有j尝试后dp[9]仍为false→ 返回false

5.2 边界处理

  • 空字符串:dp[0]=true是关键初始条件;
  • 单字符字符串:若该字符在字典中,则dp[1]=true
  • 字典为空:所有dp[i] = false(除dp[0]);

六、时间复杂度与空间复杂度分析

6.1 时间复杂度:O(n²)

  • 外层循环:n次(i从 1 到n
  • 内层循环:最多n次(j从 0 到i-1
  • 每次substring(j, i)操作:O(i - j),最坏 O(n)
  • contains()操作:O(1)(哈希表)

⚠️注意:虽然官方题解称时间复杂度为 O(n²),但实际上substring会创建新字符串,其拷贝成本为 O(k),k 为子串长度。因此严格来说,总时间复杂度为 O(n³)

但在 Java 中,String.substring在 JDK 7u6 之后已改为复制字符数组,不再共享底层数组,因此每次调用确实需要 O(k) 时间。

6.2 空间复杂度:O(n + m)

  • dp数组:O(n)
  • HashSet存储字典:O(m),其中 m 为字典总字符数(最坏 1000×20 = 20,000)
  • 总体:O(n + m),通常简化为 O(n)

七、常见问题解答(FAQ)

Q1:为什么不能直接用List.contains()

A:List.contains()的时间复杂度是 O(m),而HashSet.contains()是 O(1)。当字典很大时(如 1000 个单词),每次查询节省 O(m) 时间,对整体性能影响巨大。

Q2:内层循环为何可以break

A:因为只要找到任意一个合法的j使得dp[j] && dict.contains(...)成立,dp[i]就为true。无需检查其他j,提前终止可提升效率。

Q3:能否从右往左枚举j以优化?

A:可以!如果先知道字典中最长单词长度maxLen,则j只需从i-1枚举到max(0, i - maxLen)。因为若i - j > maxLen,子串不可能在字典中。这是一种有效剪枝。

优化版本示例

intmaxLen=0;for(Stringword:wordDict){maxLen=Math.max(maxLen,word.length());}for(inti=1;i<=n;i++){intstart=Math.max(0,i-maxLen);for(intj=start;j<i;j++){if(dp[j]&&wordSet.contains(s.substring(j,i))){dp[i]=true;break;}}}

此优化可将内层循环从 O(n) 降至 O(L),L 为最大单词长度(≤20),从而将总时间复杂度降至O(n × L),即O(300 × 20) = O(6000),远优于 O(n²)=90,000。


八、优化思路拓展

8.1 剪枝优化(基于最大单词长度)

如上所述,预计算maxLen可大幅减少无效枚举。

8.2 使用 Trie(字典树)替代 HashSet

虽然 HashSet 已足够高效,但在某些场景下(如字典极大、频繁查询前缀),Trie 更优。

Trie 优势

  • 支持前缀匹配;
  • 避免生成子串(节省内存和时间);
  • 可同时处理多个查询。

Trie 实现思路

  • 构建字典的 Trie;
  • 对每个i,从i开始向后遍历,同时在 Trie 中向下走;
  • 若遇到 Trie 中的单词结尾,且dp[j] == true,则dp[i + len] = true

但实现较复杂,且本题 n 很小(≤300),Trie 优势不明显,故一般推荐 HashSet 方案。

8.3 BFS 解法(队列+记忆化)

也可将问题视为图搜索:

  • 每个位置i是一个节点;
  • s[i..j]在字典中,则存在边i → j+1
  • 问:是否存在从 0 到 n 的路径?

使用 BFS + visited 数组同样可解,时间复杂度类似。


九、数据结构与算法基础知识点回顾

9.1 动态规划(DP)三要素

  1. 状态定义dp[i]表示什么?
  2. 状态转移方程:如何由子问题推出当前问题?
  3. 边界条件:初始值如何设定?

本题完美体现这三点。

9.2 完全背包 vs 0-1 背包

  • 0-1 背包:每个物品只能用一次 → 内层循环倒序;
  • 完全背包:物品可重复使用 → 内层循环正序;

本题中单词可重复使用,属于完全背包思想,但因字符串顺序固定,不能直接套用背包模板,需结合字符串特性设计 DP。

9.3 哈希表(HashSet)原理

  • 基于哈希函数将 key 映射到桶;
  • 平均 O(1) 插入/查找/删除;
  • 需注意哈希冲突处理(链地址法 or 开放寻址);
  • Java 中String.hashCode()是稳定的,适合做 key。

9.4 字符串子串操作成本

  • s.substring(i, j)在 Java 中会复制字符数组
  • 时间 O(j-i),空间 O(j-i);
  • 高频调用时需警惕性能瓶颈;
  • 可考虑传递起始索引+长度,避免创建新字符串(但本题简洁性优先)。

十、面试官提问环节(模拟对话)

面试官:你用了动态规划,能说说为什么不用 DFS 回溯吗?

:DFS 在最坏情况下会指数爆炸。例如s = "aaaa...aaa"dict = ["a", "aa", "aaa", ...],每一步都有多种选择,导致大量重复计算。而 DP 通过记忆化避免了重复子问题,时间复杂度可控。

面试官:你的解法时间复杂度真的是 O(n²) 吗?

:严格来说不是。因为substring操作需要 O(k) 时间,k 是子串长度。最坏情况下总时间是 O(n³)。但如果使用“最大单词长度剪枝”,可将内层循环限制在 O(L),L≤20,此时总复杂度为 O(nL),接近线性。

面试官:如果字典非常大(比如百万级),你会怎么优化?

:我会考虑以下几点:

  1. 使用Trie替代 HashSet,避免存储大量重复前缀;
  2. 在 DP 过程中,不生成子串,而是用双指针在原字符串上匹配 Trie;
  3. 预处理字典,过滤掉长度大于s.length()的单词;
  4. 若支持多线程,可并行计算不同idp[i](但依赖关系强,并行收益有限)。

面试官:这道题和“正则表达式匹配”有什么区别?

:正则匹配涉及通配符(如*,.),状态转移更复杂,通常用二维 DP。而本题是精确匹配字典单词,状态是一维的,且转移条件更简单。


十一、这道算法题在实际开发中的应用

11.1 输入法分词

中文输入法需将拼音串(如"woaini")拆分为合法词语(["wo", "ai", "ni"]),本质就是单词拆分问题。

11.2 自然语言处理(NLP)

  • 中文分词:将连续汉字序列切分为词语;
  • 命名实体识别:判断某段文本是否为已知实体(如人名、地名);
  • 敏感词过滤:检测用户输入是否包含违禁词组合。

11.3 编译器词法分析

编译器需将源代码字符串拆分为 token(如关键字、标识符、运算符),可视为带语法规则的单词拆分。

11.4 密码强度检测

某些系统要求密码必须包含特定单词组合,可用类似逻辑验证。

11.5 URL 路由匹配

Web 框架中,将 URL 路径(如/user/profile/edit)匹配到控制器,也可建模为路径拆分问题。


十二、相关题目推荐

题号题目关联点
140. 单词拆分 II返回所有可能的拆分方案本题的升级版,需回溯+记忆化
472. 连接词找出能由其他单词拼接而成的词多次调用单词拆分逻辑
648. 单词替换用最短前缀替换句子中的词Trie 应用
212. 单词搜索 II在二维网格中找字典中的词Trie + DFS
322. 零钱兑换完全背包经典题DP 思想相通

十三、总结与延伸

13.1 核心收获

  • 动态规划是解决“最优子结构+重叠子问题”的利器;
  • 状态定义是 DP 的关键,需清晰、无后效性;
  • 哈希表极大提升查询效率,是算法优化常用手段;
  • 剪枝边界分析能显著提升性能;
  • 算法题背后往往有真实工程场景支撑。

13.2 延伸思考

  • 如果字典中的单词不能重复使用,该如何修改?

    • 需记录已使用单词,状态变为(i, usedMask),复杂度飙升,可能需状态压缩 DP。
  • 如果要求输出所有拆分方案

    • 需回溯(DFS)+ 记忆化,或在 DP 基础上记录路径。
  • 如果字符串很长(如 10⁶),字典也很大?

    • 需考虑 Aho-Corasick 自动机、后缀数组等高级字符串算法。

13.3 给读者的建议

  • 不要死记硬背代码,理解状态转移的逻辑
  • 多练习“字符串 + DP”类题目,培养模式识别能力;
  • 在面试中,先写出朴素解法,再逐步优化;
  • 重视边界条件复杂度分析,这是区分初级与高级工程师的关键。

结语:LeetCode 139 “单词拆分”看似简单,实则蕴含动态规划、字符串处理、哈希优化等多重知识点。掌握它,不仅是为了通过一道面试题,更是为了构建扎实的算法思维体系。希望本文能助你在算法之路上走得更远!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1200307.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【软考每日一练015】计算机网络:DNS 递归查询与迭代查询解析

【软考每日一练015】计算机网络&#xff1a;DNS 递归查询与迭代查询解析 1. 原题目 题目&#xff1a; 主机 PC 对某个域名进行查询&#xff0c;最终由该域名的授权域名服务器解析并返回结果&#xff0c;查询过程如下图所示。这种查询方式中不合理的是&#xff08; &#xff0…

2026年最新在线客服软件与系统推荐:全面评测与选择指南

随着AI大模型与多模态技术的普及,在线客服系统已从“渠道承接”升级为“业务智能引擎”,全渠道整合、AI自主决策、数据安全合规成为企业选型核心诉求。当前市场呈现“AI原生架构为主流,垂直场景定制为补充”的格局,…

2026医学考研课程排名前十出炉!避坑指南+选课干货全整理

2026医学考研课程排名前十出炉!避坑指南+选课干货全整理宝子们!医学考研有多卷不用多说了吧?2025年医学类考研报名人数都突破123万了,较上一年增长14.7%,热门院校部分专业报录比甚至超过10:1。想在千军万马中成功…

近6亿元!欧洲航天局站台,这家瑞士企业用3D打印重构卫星制造

不只是火箭&#xff0c;3D打印也正在造“卫星”。 2026年1月22日&#xff0c;据资源库了解&#xff0c;欧洲卫星系统与射频&#xff08;RF&#xff09;产品制造商SWISSto12宣布&#xff0c;通过欧洲航天局&#xff08;ESA&#xff09;旗下ARTES HummingSat合作项目&#xff0c;…

arcGis连不上HighGoDB的解决方案

文章目录 环境文档用途详细信息相关文档 环境 系统平台&#xff1a; 版本&#xff1a;4.7.6 文档用途 本文档主要介绍如何使得arcGis客户端能正确的连接HighGoDB数据库 详细信息 问题情况&#xff1a; 解决办法&#xff1a; 第一步&#xff1a;HighGoDB–>ArcGis的dl…

2026论文降AIGC率工具排行榜,CSDN权威评测AI率狂降至8%,多平台通杀还加密防漏!

作为常年和论文、文案打交道的“AI检测闯关人”,2025-2026年实测了20+款降AIGC率工具,踩过机械改写的坑,也挖到了真宝藏神器。2026年知网、维普等AI检测算法又双叒升级,单纯同义词替换早已失效,这份CSDN、凤凰网等…

Java版LeetCode热题100之最长递增子序列:从O(n²)动态规划到O(n log n)贪心+二分的深度剖析

Java版LeetCode热题100之最长递增子序列&#xff1a;从O(n)动态规划到O(n log n)贪心二分的深度剖析 本文全面解析 LeetCode 第300题「最长递增子序列」&#xff08;Longest Increasing Subsequence, LIS&#xff09;&#xff0c;涵盖题目理解、两种经典解法&#xff08;DP与贪…

大数据毕业设计选题推荐:基于Spark+Django的旅游评价分析系统源码 毕业设计 选题推荐 毕设选题 数据分析 机器学习 数据挖掘

✍✍计算机毕设指导师** ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡有什么问题可以…

Java版LeetCode热题100之乘积最大子数组:动态规划中的正负博弈与空间优化艺术

Java版LeetCode热题100之乘积最大子数组&#xff1a;动态规划中的正负博弈与空间优化艺术 本文深入剖析 LeetCode 第152题「乘积最大子数组」&#xff0c;从问题本质出发&#xff0c;详解为何普通最大子序和思路失效&#xff0c;如何通过维护最大/最小值双状态解决负数翻转问题…

2026年高适配工单系统品牌厂商盘点,靠谱推荐清单

2026年,企业数字化协同进入深水区,工单系统作为连接服务需求与执行闭环的核心工具,已从单一客服场景延伸至全业务链路。随着跨部门协作、现场服务等需求激增,市场对系统的合规性、智能化、集成能力要求显著提升,一…

Java版LeetCode热题100之分割等和子集:从NP完全问题到0-1背包的深度解析

Java版LeetCode热题100之分割等和子集&#xff1a;从NP完全问题到0-1背包的深度解析 本文全面剖析 LeetCode 第416题「分割等和子集」&#xff0c;这是一道经典的 NP 完全问题&#xff0c;可转化为 0-1 背包模型。文章涵盖题目理解、动态规划建模、二维与一维DP实现、复杂度分析…

【译】Visual Studio 2026 来了:更快、更智能,深受老用户的喜爱

我们非常激动地宣布,Visual Studio 2026 现已正式发布!这一刻是我们与您携手共创的成果。您的反馈对本次版本的塑造作用超过了以往任何一次。自 2025 年 9 月推出 Insiders 通道以来,下载并测试该预览版的开发者数量…

医学考研党必看!这些资料带你稳稳上岸

医学考研党必看!这些资料带你稳稳上岸医学考研内卷有多激烈不用多说吧?每年都是千军万马过独木桥,选对资料直接少走一半弯路!作为深耕教育领域的博主,今天就把私藏的医学考研资料清单整理出来了,从基础到冲刺全覆…

Java锁优化:从synchronized到CAS的演进与实战选择

文章目录 &#x1f4ca;&#x1f4cb; 一、 序言&#xff1a;线程同步的“速度与激情”&#x1f30d;&#x1f4c8; 二、 深度拆解&#xff1a;synchronized的锁升级之路&#x1f6e1;️&#x1f9e9; 2.1 锁的物理载体&#xff1a;Mark Word&#x1f504;&#x1f9f1; 2.2 演…

第六课 · 6.1从 JDBC 到 MyBatis:SQL 工程化是如何发生的?

如果说 ORM 是“对象如何存在于数据库中的体系”&#xff0c; 那 MyBatis&#xff0c;就是这套体系中最靠近数据库的一条工程路线。这一篇不讲 XML 怎么写&#xff0c;不讲分页插件&#xff0c;不教 CRUD。 我们只回答一个问题&#xff1a;&#x1f449; 为什么 JDBC 一定会进化…

Java版LeetCode热题100之最长有效括号:三种解法深度剖析与算法思维升华

Java版LeetCode热题100之最长有效括号&#xff1a;三种解法深度剖析与算法思维升华 本文全面解析 LeetCode 第32题「最长有效括号」&#xff0c;这是一道考察字符串处理、动态规划、栈应用及双指针技巧的经典难题。文章涵盖题目理解、三种主流解法&#xff08;DP、栈、双指针&a…

2026申请香港优才中介机构有哪些:从政策适配到服务全面对比

2026想办理香港优才申请中介推荐哪家?寰行盛世成为众多精英的首选香港身份服务机构。 2026年,香港优才计划(优秀人才入境计划)凭借“无投资、无雇主要求、无名额限制”的核心优势,仍是内地人才赴港的首选路径。但…

客船按需定制厂家怎么选?青岛雷旺达船舶是优选

在水上旅游蓬勃发展的当下,一艘契合景区特色与运营需求的客船,是提升游客体验、拓展业务边界的核心载体。面对市场上琳琅满目的客船制造商,如何挑选兼具品质、定制化能力与售后保障的合作伙伴?以下结合客船按需定制…

学长亲荐2026TOP10AI论文工具:本科生毕业论文必备测评

学长亲荐2026TOP10AI论文工具&#xff1a;本科生毕业论文必备测评 2026年学术AI写作工具测评&#xff1a;为何值得一看&#xff1f; 随着人工智能技术的不断进步&#xff0c;AI论文工具在学术领域的应用越来越广泛。对于本科生而言&#xff0c;撰写毕业论文不仅是学业的重要环节…

wsl2使用windows代理

1.查看wsl和windows版本号: wsl --version WSL 版本: 2.5.10.0 内核版本: 6.6.87.2-1 WSLg 版本: 1.0.66 MSRDC 版本: 1.2.6074 Direct3D 版本: 1.611.1-81528511 DXCore 版本: 10.0.26100.1-240331-1435.ge-release …