【力扣100题】46.单词拆分

题目描述

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s,则返回 true。

注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

示例:

  • 输入:s = "leetcode", wordDict = ["leet", "code"] → 输出:true("leetcode" 可以由 "leet" 和 "code" 拼接成)
  • 输入:s = "applepenapple", wordDict = ["apple", "pen"] → 输出:true("applepenapple" 可以由 "apple" "pen" "apple" 拼接成)
  • 输入:s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"] → 输出:false

解题思路总览

方法 思路 时间复杂度 空间复杂度
动态规划 dp[i] 表示字符串前 i 个字符能否被拆分,状态转移 dp[i] = dp[j] && wordDict.contains(s[j:i]) O(n^2 × L) O(n)
BFS 从起点开始,用字典中的单词扩展,能到达终点则返回 true O(n × L) O(n)
Trie + BFS 用字典树优化字符串查找,加速 BFS O(n × L) O(n + dict_size)

本题采用动态规划方法。


完整代码

cpp 复制代码
class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        unordered_set<string> set(wordDict.begin(), wordDict.end());
        int n = s.size();
        vector<bool> dp(n + 1, false);
        dp[0] = true;
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < i; j++) {
                if (dp[j] && set.find(s.substr(j, i - j)) != set.end()) {
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[n];
    }
};

算法流程图

复制代码
输入: s = "leetcode", wordDict = ["leet", "code"]

初始化:
  set = {"leet", "code"}
  n = 8
  dp[0] = true
  dp[1...8] = false

i = 1:
  j = 0: dp[0]=true, 检查 s[0:1] = "l"
    "l" 不在 set 中, 继续
  j = 1: dp[1]=false, 跳过
  ... (j=2...7 同理)
  dp[1] = false

i = 2:
  j = 0: dp[0]=true, 检查 s[0:2] = "le"
    "le" 不在 set 中, 继续
  j = 1: dp[1]=false, 跳过
  ... (j=2...7 同理)
  dp[2] = false

i = 4:
  j = 0: dp[0]=true, 检查 s[0:4] = "leet"
    "leet" 在 set 中!
    dp[4] = true, break
  dp[4] = true

i = 8:
  j = 0: dp[0]=true, 检查 s[0:8] = "leetcode"
    "leetcode" 不在 set 中, 继续
  j = 1: dp[1]=false, 跳过
  j = 2: dp[2]=false, 跳过
  j = 3: dp[3]=false, 跳过
  j = 4: dp[4]=true, 检查 s[4:8] = "code"
    "code" 在 set 中!
    dp[8] = true, break
  dp[8] = true

最终 dp[8] = true
输出: true

逐行解析

cpp 复制代码
unordered_set<string> set(wordDict.begin(), wordDict.end());

含义: 将字典列表转换为哈希集合,提高单词查找效率,O(1) 时间判断一个字符串是否在字典中。

cpp 复制代码
int n = s.size();

含义: 记录字符串长度,方便后续循环使用。

cpp 复制代码
vector<bool> dp(n + 1, false);

含义: 创建大小为 n+1 的布尔数组,dp[i] 表示字符串前 i 个字符能否被拆分成字典中的单词。dp[0] = true 表示空字符串可以被拆分(作为起始条件)。

cpp 复制代码
dp[0] = true;

含义: 基础情况,空字符串可以被拆分,这是一个重要的递推起点。

cpp 复制代码
for (int i = 1; i <= n; i++)

含义: 从 1 到 n 依次计算每个前缀是否能被拆分。dp[i] 对应字符串 s[0:i)。

cpp 复制代码
for (int j = 0; j < i; j++)

含义: 枚举所有可能的分割点 j,将前 i 个字符拆分为 s[0:j) 和 s[j:i) 两部分。

cpp 复制代码
if (dp[j] && set.find(s.substr(j, i - j)) != set.end())

含义: 状态转移条件。只有当左边部分 s[0:j) 能被拆分(dp[j] = true)且右边部分 s[j:i) 在字典中时,前 i 个字符才能被拆分。

cpp 复制代码
dp[i] = true;
break;

含义: 找到一个可行的分割点后,直接 break 跳出内层循环,因为只需要知道是否存在可行方案,不需要继续枚举。

cpp 复制代码
return dp[n];

含义: 返回字符串前 n 个字符(即整个字符串)是否能被拆分。


复杂度分析

复杂度 值 说明
时间复杂度 O(n^2 × L) 外层循环 n 次,内层循环最多 n 次,每次 substr 和 find 操作 O(L),L 为字符串平均长度
空间复杂度 O(n + dict_size) dp 数组 O(n),哈希集合存储字典 O(dict_size)

面试追问 FAQ

问题 答案
为什么不直接在内层循环中判断 s[j:i] 是否在字典? 因为 Java/Python 的字符串切片是 O(L),C++ 的 substr 也是 O(L),所以总复杂度是 O(n^2 × L)
如何优化到 O(n × L)? 使用 Trie(字典树)存储字典,在 dp 过程中从位置 j 开始在 Trie 中匹配,而不是每次都用 substr
dp[0] = true 是什么意思? 表示空字符串可以被拆分,作为递推的起点。例如要拆分 "leetcode",当 j=4 时,检查 "leet" 是否在字典
完全背包和这道题有什么关系? 这道题本质上是字典的完全背包问题:字典中的单词是"物品",字符串是"背包容量",但关注的是能否装满
如果字典中有大量单词,如何优化空间? 使用 Trie 代替哈希集合,可以将字典的空间从 O(dict_size × L) 优化到 O(dict_size × L) 但查找更快
如何输出具体的拆分方案? 额外记录每个 dp[i] = true 是从哪个 j 转移来的,最后从 dpn 回溯得到所有单词

相关题目

题号 题目 难度 核心思路
139 单词拆分 中等 完全背包/字典树
140 单词拆分 II 困难 字典树 + DFS
322 零钱兑换 中等 完全背包
279 完全平方数 中等 动态规划

总结

要点 内容
核心思想 完全背包动态规划,判断字符串前缀能否被字典拆分
状态定义 dp[i] = 字符串前 i 个字符能否被拆分成字典中的单词
状态转移 dp[i] = dp[j] && wordDict.contains(s[j:i])
初始化 dp[0] = true(空字符串可以被拆分)
关键技巧 一旦找到可行分割点就 break,减少无效计算

相关推荐
hanlin0323 分钟前
刷题笔记:力扣第32题-最长有效括号
笔记·算法·leetcode
All for pursuit.44 分钟前
【动态规划-21】312.戳气球
数据结构·c++·算法·leetcode·动态规划
碧海银沙音频科技研究院3 小时前
基于炬芯ATS3089X自研AEC声学回声消除算法
算法
通问AI3 小时前
在两台 Mac Studio 上跑 800B 模型:MLX + RDMA over Thunderbolt 实测与四道门选型法
算法
ZywOo6233 小时前
《算法竞赛进阶指南》0x03 前缀和与差分
算法
Scintilla_y4 小时前
DAY2 滑动窗口
算法·leetcode·散列表
lvwangshu4 小时前
笛卡尔树
算法·图论
026d234 小时前
状态信号只知“其然”不知“其所以然”:变工况机械故障诊断的物理盲区
算法
姜鱼问生4 小时前
备份和服务器在同一台机器上,等于没备份
算法·架构
姜鱼问生4 小时前
别再 chmod 777 了,用 ACL 给它开一扇窗
后端·算法·架构