单词拆分(Word Break)题解 | 动态规划解法

单词拆分(Word Break)题解 | 动态规划解法

问题描述

给定一个非空字符串 s 和一个包含若干非空单词的字符串列表 wordDict(字典),判断是否可以将字符串 s 拆分为若干个字典中的单词(字典中的单词可重复使用,无需用完所有单词)。

核心特征分析

本题的核心是字符串拆分的可达性判断,核心特征可归纳为:

  1. 问题本质:判断整个字符串能否被划分为字典中单词的组合,属于"可达性"判定问题(而非求所有拆分方案);
  2. 重叠子问题:判断前 i 个字符能否拆分时,会重复用到前 jj < i)个字符的拆分结果,若暴力计算会重复求解子问题;
  3. 最优子结构:前 i 个字符的拆分结果,可由"前 j 个字符可拆分"且"ji 之间的子串在字典中"两个条件推导得出。

算法选择

可选算法对比

  • 暴力搜索/回溯:可枚举所有可能的拆分方式,但会重复计算大量子问题(如多次判断前 k 个字符能否拆分),时间复杂度呈指数级,效率极低;
  • 动态规划(DP):通过缓存子问题的解(DP数组)避免重复计算,仅需线性空间存储状态,时间复杂度可优化至多项式级别。

最终选择

优先选择动态规划,原因:题目仅需判断"能否拆分"而非"所有拆分方案",DP可通过状态缓存高效解决重叠子问题,是此类可达性问题的最优选择。

解题模式识别(动态规划适用场景)

当遇到以下特征的字符串问题时,可优先考虑动态规划:

  1. 问题目标是"判断能否完成某类组合/分割",而非求所有解或最优解;
  2. 子问题的解可推导原问题的解,且存在明确的最优子结构;
  3. 字符串拆分/分割类问题(如分割回文串、单词拆分等),通常存在重叠子问题。

解题思路

动态规划的核心是定义状态并推导转移方程,具体步骤如下:

  1. 预处理字典 :将 wordDict 转换为哈希集合(unordered_set),将子串查询的时间复杂度从 O(m)m 为字典长度)降至 O(1)
  2. 定义DP状态 :设 dp[i] 表示"字符串 s 的前 i 个字符(即 s[0...i-1])能否被拆分为字典中的单词";
  3. 初始化状态dp[0] = true(空字符串默认可拆分,作为递归/迭代的起始条件);
  4. 状态转移
    • 遍历字符串长度 i(从1到 s 的总长度 n),逐一判断前 i 个字符的可达性;
    • 对每个 i,遍历分割点 j(从0到 i-1),若满足两个条件:
      • dp[j] = true(前 j 个字符可拆分);
      • 子串 s[j...i-1](即 s.substr(j, i-j))存在于字典中;
    • 则说明前 i 个字符可拆分,令 dp[i] = true(找到一种可行方案即可,无需继续遍历 j);
  5. 返回结果 :最终 dp[n] 即为整个字符串 s 的拆分结果(ns 的长度)。

解题代码(带详细注释)

cpp 复制代码
class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        int n = s.size();
        // 1. 字典转哈希集合,优化查询效率
        unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
        // 2. 定义dp数组:dp[i]表示前i个字符能否被拆分
        vector<bool> dp(n + 1, false);
        // 3. 初始状态:空字符串可拆分
        dp[0] = true;

        // 4. 遍历所有可能的字符串长度i(前i个字符)
        for (int i = 1; i <= n; ++i) {
            // 遍历所有可能的分割点j
            for (int j = 0; j < i; ++j) {
                // 核心条件:前j个字符可拆分 + j到i的子串在字典中
                if (dp[j] && wordSet.count(s.substr(j, i - j))) {
                    dp[i] = true;
                    break; // 找到一种方案即可,无需继续遍历j
                }
            }
        }

        // 5. 返回整个字符串的拆分结果
        return dp[n];
    }
};

复杂度分析

时间复杂度

O(n²),其中 n 是字符串 s 的长度:

  • 外层循环遍历 i(1到 n),共 n 次;
  • 内层循环遍历 j(0到 i-1),最坏情况下总遍历次数为 1+2+...+n = n(n+1)/2,近似为 O(n²)
  • 哈希集合的查询操作是 O(1),子串截取 s.substr(j, i-j) 的时间复杂度为 O(i-j),但由于字典中单词长度通常远小于 n,实际时间复杂度接近 O(n²)

空间复杂度

O(n)

  • 主要开销为 dp 数组(长度 n+1),哈希集合的空间取决于字典大小(题目未要求优化字典空间,属于输入相关开销)。

总结

  1. 本题核心是利用动态规划解决字符串拆分的可达性问题 ,通过 dp[i] 缓存前 i 个字符的拆分状态,避免重复计算;
  2. 关键优化点:将字典转为哈希集合,将子串查询效率从 O(m) 降至 O(1)
  3. 状态转移的核心逻辑:dp[i] = dp[j] && (s[j:i] ∈ 字典),找到任意一个可行的 j 即可确定 dp[i] = true
相关推荐
翱翔的苍鹰2 小时前
使用PyTorch实现线性回归的完整流程
算法·回归·线性回归
万行2 小时前
机器人系统ros2&期末速通2
前端·人工智能·python·算法·机器学习
qq_433554542 小时前
C++ 图论算法:二分图最大匹配
c++·算法·图论
MSTcheng.2 小时前
【算法】滑动窗口解决力扣『将x减到0的最操作数』问题
算法·leetcode·职场和发展
静心问道2 小时前
动态规划分类及算法实现
算法·分类·动态规划
bbq粉刷匠2 小时前
Java—排序1
数据结构·算法·排序算法
jghhh012 小时前
基于MATLAB的分块压缩感知程序实现与解析
开发语言·算法·matlab
智驱力人工智能2 小时前
视觉分析赋能路面漏油检测 从产品设计到城市治理的实践 漏油检测 基于YOLO的漏油识别算法 加油站油罐泄漏实时预警技术
人工智能·opencv·算法·yolo·目标检测·计算机视觉·边缘计算
%xiao Q2 小时前
信息学奥赛一本通(部分题解)
c语言·c++·算法