当前位置: 首页 > news >正文

LeetCode算法日记 - Day 87: 单词拆分

目录

1. 单词拆分

1.1 题目解析

1.2 解法

1.3 代码实现


1. 单词拆分

https://leetcode.cn/problems/word-break/description/

给你一个字符串 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 中的所有字符串 互不相同

1.1 题目解析

题目本质
判断字符串能否由字典中的单词拼接而成。本质是"字符串分割验证"问题,核心在于找到合法的分割点,使得每一段都是字典单词。

常规解法
最直观的想法是递归回溯,尝试所有可能的分割方案:从左到右枚举每个位置,如果前缀在字典中,就递归检查剩余部分。例如 "leetcode",先试 "l"、"le"、"lee"、"leet",如果 "leet" 在字典,再递归检查 "code"。

问题分析
递归回溯会产生大量重复计算。例如 s = "aaaaaab",字典 = ["a", "aa"],检查后半部分时会重复判断同样的子串很多次。时间复杂度最坏达到 O(2^n),对于长度 300 的字符串会超时。

思路转折
要想高效 → 必须避免重复计算 → 动态规划。
关键观察:如果我们已经知道前 j 个字符能拆分,那么只需判断 [j, i] 这段是否在字典中,就能知道前 i 个字符能否拆分。这样,每个位置只计算一次,通过"记录历史结果"来加速判断。

1.2 解法

算法思想: 动态规划。定义 dp[i] 表示字符串前 i 个字符能否被拆分成字典单词。对于每个位置 i,枚举所有可能的分割点 j(0 ≤ j < i),如果前 j 个字符能拆分(dp[j] = true)且 [j, i) 这段在字典中,则 dp[i] = true。

状态转移方程:

dp[i] = true  当存在 j (0 ≤ j < i) 满足:dp[j] = true && s[j...i-1] ∈ wordDict

i)优化查找: 将字典转为 HashSet,使查找时间从 O(n) 降到 O(1)

ii)保存原长度: 用变量 n 保存字符串原始长度,因为后续要修改字符串

iii)初始化 dp: 创建长度为 n+1 的布尔数组,dp[0] = true 表示空字符串可以拆分

iv)处理下标: 在字符串前加空格 s = " " + s,使 dp 数组下标与字符串下标对齐,方便截取

v)双层循环枚举:

  • 外层 i 从 1 到 n:表示检查前 i 个字符

  • 内层 j 从 i 到 1 倒序:表示尝试在 j 处分割

vi)判断并更新: 如果 dp[j-1] 为 true 且 s.substring(j, i+1) 在字典中,则 dp[i] = true,并 break 优化

易错点

  • substring 左闭右开: s.substring(j, i+1) 实际截取 [j, i] 范围的字符,因为 Java substring 的 end 参数不包含在结果中

  • 先保存原长度: 必须在 s = " " + s 之前执行 int n = s.length(),否则 n 会多 1,导致数组越界

  • i 和 j 的含义: i 是检查的终点位置(前 i 个字符),j 是尝试的分割点,将字符串分成 [0, j-1] 和 [j, i] 两部分

  • dp 长度问题: dp 数组长度是 n+1(基于原始字符串长度),因为 dp[0] 表示空串,dp[n] 表示整个字符串

  • break 的作用: 找到一种拆分方法后立即跳出内层循环,避免重复设置 dp[i],提升性能

1.3 代码实现

class Solution {public boolean wordBreak(String s, List<String> wordDict) {// 优化:HashSet 查找 O(1)Set<String> hash = new HashSet<>(wordDict);// 先保存原长度(关键)int n = s.length();// dp[i]: 前 i 个字符能否拆分boolean[] dp = new boolean[n + 1];dp[0] = true;  // 空串可以拆分// 加空格对齐下标s = " " + s;for (int i = 1; i <= n; i++) {for (int j = i; j >= 1; j--) {// 前半部分能拆分 && 后半部分在字典if (dp[j-1] && hash.contains(s.substring(j, i+1))) {dp[i] = true;break;  // 找到即停止}}}return dp[n];}
}

复杂度分析

  • 时间复杂度: O(n² × L),其中 n 是字符串长度,L 是单词平均长度。两层循环 O(n²),substring 和字符串比较需要 O(L)

  • 空间复杂度: O(n + m),其中 n 是 dp 数组大小,m 是字典中所有单词的总长度(HashSet 存储)

http://www.dtcms.com/a/545621.html

相关文章:

  • 学术论文写作与发表精讲:融合AI工具的高效方法与实战案例
  • 天津开发网站公司虚拟主机如何建设多个网站
  • 跟公司产品做网站制作app软件工具免费
  • 133-Spring AI Alibaba Vector Redis 功能完整案例
  • 线段树详解
  • AI 大模型应用中的图像,视频,音频的处理
  • 2025年大专建筑工程技术专业前景!
  • @1Panel 全面指南:从部署到高阶使用
  • SAP MM 采购申请转采购订单功能分享
  • FPGA设计中的“幽灵信号:一条走线,两种命运——浅析路径延迟导致的逻辑错误
  • 网站建设将新建用户授权为管理员免费搭建手机网站源码
  • 北京企业网站建设费用新闻最新消息
  • 算法工具箱之二分查找
  • undefined reference to `cJSON_AddStringToObject‘
  • 仓颉语言中TreeMap红黑树结构的实现与优化
  • Rust 的构建系统和包管理器
  • AI驱动嵌入式软件全链路变革:从芯片到系统的深度智能重构
  • 怎么修改网站域名推广网站排行榜
  • 靠谱的电磁阀维护保养
  • 【自动化测试函数 (下)】Web自动化攻坚:应对加载等待、浏览器导航与无头模式的自动化脚本艺术
  • 正向代理工具
  • 攀枝花建设工程有限责任公司网站中国兰州网
  • Kubernetes 部署
  • 网站建设投标人资质要求wordpress邮箱如何解析
  • 鞍山商城网站建设运城手机网站制作
  • 【GitLab/CD】前端 CD
  • 做简报的网站竹制品网站怎么做
  • Kafka使用-Consumer
  • 诸暨网站建设怎么建立微网站?
  • 【Docker】【1.docker常用命令总结】