LeetCode - #139 单词拆分


文章目录

前言

本题由于没有合适答案为以往遗留问题,最近有时间将以往遗留问题一一完善。

我们社区陆续会将顾毅(Netflix 增长黑客,《iOS 面试之道》作者,ACE 职业健身教练。)的 Swift 算法题题解整理为文字版以方便大家学习与阅读。

不积跬步,无以至千里;不积小流,无以成江海,Swift社区 伴你前行。如果大家有建议和意见欢迎在文末留言,我们会尽力满足大家的需求。

难度水平:中等

摘要

1. 描述

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

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

2. 示例

示例 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
  • swordDict[i] 仅由小写英文字母组成
  • wordDict 中的所有字符串 互不相同

3. 答案

题解

我们可以使用动态规划(Dynamic Programming, DP)来解决该问题。

动态规划的思路

  1. 定义状态:

    • 用一个布尔数组 dp 表示字符串的可拼接状态。
    • dp[i] 表示字符串 s[0..<i] 是否可以由字典中的单词拼接而成。
  2. 状态转移方程:

    • 如果存在一个 j,使得 dp[j] == trues[j..<i] 是字典中的一个单词,则 dp[i] = true
    • 换句话说,dp[i] 取决于之前某个位置 j 的状态和当前子字符串是否在字典中。
  3. 初始化:

    • dp[0] = true,表示空字符串可以被拼接。
  4. 结果:

    • 最终答案是 dp[s.count]

代码实现

swift 复制代码
func wordBreak(_ s: String, _ wordDict: [String]) -> Bool {
    // 将 wordDict 转换为 Set,方便快速查询单词是否存在
    let wordSet = Set(wordDict)
    let n = s.count
    // 初始化 DP 数组,默认值为 false
    var dp = Array(repeating: false, count: n + 1)
    dp[0] = true // 空字符串可以被拼接

    // 将字符串转换为字符数组,便于索引操作
    let sArray = Array(s)

    // 遍历字符串长度
    for i in 1...n {
        // 检查所有可能的子字符串
        for j in 0..<i {
            // 子字符串 s[j..<i]
            let substring = String(sArray[j..<i])
            if dp[j] && wordSet.contains(substring) {
                dp[i] = true
                break
            }
        }
    }
    return dp[n]
}

代码解析

1. wordDict 转换为 Set
swift 复制代码
let wordSet = Set(wordDict)

将字典转换为 Set,可以将查找时间从 O(k) 降低到 O(1),其中 k 是字典中单词的个数。

2. 初始化 DP 数组
swift 复制代码
var dp = Array(repeating: false, count: n + 1)
dp[0] = true
  • dp[i] 的值表示从字符串的起始到第 i 个字符(不含 i)的子字符串是否可以拼接。
  • 初始状态 dp[0] = true 表示空字符串总是可以拼接。
3. 状态转移方程
swift 复制代码
for i in 1...n {
    for j in 0..<i {
        let substring = String(sArray[j..<i])
        if dp[j] && wordSet.contains(substring) {
            dp[i] = true
            break
        }
    }
}
  • 对于每个位置 i,检查从 ji 的子字符串 s[j..<i] 是否存在于字典中。
  • 如果存在,并且 dp[j] == true,说明从 0..<j 的部分可以拼接,则更新 dp[i] = true
4. 返回结果
swift 复制代码
return dp[n]

dp[n] 表示整个字符串是否可以拼接。

测试用例

示例 1:
swift 复制代码
let s = "leetcode"
let wordDict = ["leet", "code"]
print(wordBreak(s, wordDict)) // 输出: true

解释 : s 可以拆分为 "leet" 和 "code",两者都在字典中。

示例 2:
swift 复制代码
let s = "applepenapple"
let wordDict = ["apple", "pen"]
print(wordBreak(s, wordDict)) // 输出: true

解释 : s 可以拆分为 "apple"、"pen" 和 "apple",所有单词都在字典中。

示例 3:
swift 复制代码
let s = "catsandog"
let wordDict = ["cats", "dog", "sand", "and", "cat"]
print(wordBreak(s, wordDict)) // 输出: false

解释 : 无法将 s 拆分成字典中的单词组合。

时间复杂度

  1. 外层循环 :
    • 遍历字符串长度 n
  2. 内层循环 :
    • 遍历每个子字符串 ji,最多运行 n 次。
  3. 子字符串查找 :
    • 查找操作在字典中为 O(1)

总时间复杂度为 O(n²)

空间复杂度

  • DP 数组占用 O(n)
  • 转换的 wordSet 占用 O(k) ,其中 k 是字典中单词的个数。

总空间复杂度为 O(n + k)

总结

  • 本题通过动态规划的方法,利用子问题的结果推导出整体结果,避免了重复计算。
  • 关键在于正确理解状态转移方程,以及将问题分解为可复用的子问题。
  • 代码简洁,适合处理字符串拼接类问题。

关于我们

我们是由 Swift 爱好者共同维护,我们会分享以 Swift 实战、SwiftUI、Swift 基础为核心的技术内容,也整理收集优秀的学习资料。

相关推荐
鸽鸽程序猿12 分钟前
【算法】【优选算法】宽搜(BFS)中队列的使用
算法·宽度优先·队列
Jackey_Song_Odd13 分钟前
C语言 单向链表反转问题
c语言·数据结构·算法·链表
Watermelo61716 分钟前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
乐之者v22 分钟前
leetCode43.字符串相乘
java·数据结构·算法
A懿轩A1 小时前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
古希腊掌管学习的神1 小时前
[搜广推]王树森推荐系统——矩阵补充&最近邻查找
python·算法·机器学习·矩阵
云边有个稻草人1 小时前
【优选算法】—复写零(双指针算法)
笔记·算法·双指针算法
半盏茶香1 小时前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
忘梓.2 小时前
解锁动态规划的奥秘:从零到精通的创新思维解析(3)
算法·动态规划
tinker在coding4 小时前
Coding Caprice - Linked-List 1
算法·leetcode