【力扣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 次,每次 substrfind 操作 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 转移来的,最后从 dp[n] 回溯得到所有单词

相关题目

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

总结

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

相关推荐
MicroTech20251 小时前
量子安全赋能协同智能,微算法科技(NASDAQ :MLGO)研发PQS-BFL后量子区块链联邦学习框架
科技·算法·安全
平行侠2 小时前
A19 工业设备故障决策树智能诊断系统
算法·决策树·机器学习
铮铭2 小时前
【论文阅读】世界模型发展脉络整理---Understanding World or Predicting Future? A Comprehensive Survey of World Models
论文阅读·人工智能·算法·机器人
灵智实验室2 小时前
PX4状态估计技术EKF2详解(四):EKF2 Output Predictor——从延迟估计到实时输出
算法·无人机·px 4
科研小白_3 小时前
【MATLAB点云处理基础】基于区域生长算法的桥墩面域点云分割
算法
paeamecium3 小时前
【PAT甲级真题】- Shuffling Machine (20)
c++·算法·pat考试·pat
m0_737539373 小时前
pod Scheduler调度
算法·贪心算法
此生决int3 小时前
算法从入门到精通——双指针
算法
普马萨特3 小时前
Uber H3:地理网格索引在空间数据分析中的应用
数据结构·算法