单词拆分——LeetCode

139.单词拆分

题目

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

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

示例 1:

输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。

解题思路

  1. 动态规划 :使用动态规划的思想,从左到右遍历字符串 s,判断每个前缀是否可由 wordDict 中的单词拼接而成。

  2. 状态定义 :定义一个布尔数组 canBreak[i] 表示字符串 s 的前 i 个字符是否可拼接。

  3. 初始化canBreak[0] 初始化为 true,表示空字符串始终可拼接。

  4. 状态转移 :对于每个索引 i(1 到 s.length()),检查 s 从索引 0i-1 的每个可能前缀是否在字典中,并且前缀的结尾字符与当前索引 i 处的字符相匹配。

解题过程

  1. 初始化一个长度为 s.length() + 1 的布尔数组 canBreak,并设置 canBreak[0]true

  2. 使用两层循环:外层循环遍历字符串 s 的每个索引 i。内层循环从 0 遍历到 i-1,尝试找到所有可能的前缀。

  3. 在内层循环中,如果 canBreak[j]trues 从索引 ji 的子字符串在字典 wordDict 中,则设置 canBreak[i]true 并跳出内层循环。

  4. 继续外层循环直到遍历完所有索引。

  5. 最后,返回 canBreak[s.length()],表示整个字符串 s 是否可拼接。

复杂度

  • 时间复杂度 :最坏情况下,算法需要检查字符串 s 的所有子字符串是否在字典 wordDict 中。对于长度为 n 的字符串,有 O(n^2) 个子字符串,每个子字符串的查找操作的时间复杂度为 O(m)(其中 m 是字典 wordDict 的大小)。因此,总时间复杂度为 O(n^2 * m)

  • 空间复杂度 :使用了一个长度为 n + 1 的布尔数组来存储中间结果,空间复杂度为 O(n)

Code

java 复制代码
class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {

        boolean[] canBreak = new boolean[s.length() + 1];

        canBreak[0] = true;
        
        for (int i = 1; i <= s.length(); i++) {
            
            for (int j = 0; j < i; j++) {
               
                if (canBreak[j] && wordDict.contains(s.substring(j, i))) {
                    canBreak[i] = true;
                    break;
                }
            }
        }

        return canBreak[s.length()];
    }
}

140.单词拆分II

题目

给定一个字符串 s 和一个字符串字典 wordDict ,在字符串 s 中增加空格来构建一个句子,使得句子中所有的单词都在词典中。以任意顺序 返回所有这些可能的句子。

**注意:**词典中的同一个单词可能在分段中被重复使用多次。

示例 1:

输入: s = "catsanddog", wordDict = ["cat","cats","and","sand","dog"]
输出:["cats and dog","cat sand dog"]

解题思路

问题定义 :给定一个字符串 s 和一个字符串字典 wordDict,任务是在字符串 s 中增加空格来构建一个句子,使得句子中所有的单词都在字典 wordDict 中。这个问题可以通过回溯算法来解决。回溯算法是一种通过探索所有可能的候选解来找出所有解决方案的方法。为了避免重复计算相同子问题的解,我们使用记忆化技术来存储已经解决的子问题的解。

解题过程

  1. 初始化 :创建一个 memo 哈希表来存储已经计算过的子问题的解。

  2. 递归函数 :实现一个 backtrack 函数,该函数尝试在字符串 s 的不同位置添加空格,以找到所有可能的分割方式。

  3. 终止条件 :如果当前索引 start 等于字符串 s 的长度,返回一个只包含空字符串的列表,表示已经到达字符串末尾。

  4. 记忆化 :在 backtrack 函数中,首先检查 memo 是否已经包含了当前 start 索引的解,如果包含,则直接返回该解。

  5. 回溯搜索 :遍历字符串 s 从当前索引 start 到字符串末尾的每个位置 end,检查 sstartend 的子字符串是否在字典 wordDict 中。

  6. 递归调用 :对于每个有效的子字符串,递归调用 backtrack 函数,使用 end 作为新的起始索引。

  7. 结果合并:将当前有效的子字符串与递归调用的结果合并,构建完整的句子。

  8. 存储结果 :将当前 start 索引的所有可能解存储在 memo 中,以便后续使用。

复杂度

  • 时间复杂度 :最坏情况下,算法需要遍历字符串 s 的所有可能的子字符串。对于长度为 n 的字符串,有 O(n^2) 个子字符串。对于每个子字符串,我们需要 O(k) 的时间来检查它是否存在于字典中,其中 k 是字典中单词的平均长度。因此,最坏情况下的时间复杂度是 O(n^2 * k)。然而,由于记忆化减少了重复计算,实际的时间复杂度可能会更低。

  • 空间复杂度 :空间复杂度主要由存储子问题解的 memo 哈希表决定。在最坏的情况下,我们可能需要存储每个子问题的解,这将需要 O(n * k) 的空间。此外,递归调用栈的空间复杂度也是 O(n)。

Code

java 复制代码
class Solution {
    public List<String> wordBreak(String s, List<String> wordDict) {
        Set<String> wordSet = new HashSet<>(wordDict);
        Map<Integer, List<String>> memo = new HashMap<>();
        return backtrack(s, 0, wordSet, memo);
    }

    private List<String> backtrack(String s, int start, Set<String> wordSet, Map<Integer, List<String>> memo) {
        if (start == s.length()) {
            return new ArrayList<>(Collections.singletonList("")); // 返回包含空字符串的列表
        }

        // 如果结果已经被计算过,直接从记忆化存储中获取结果
        if (memo.containsKey(start)) {
            return memo.get(start);
        }

        List<String> result = new ArrayList<>();
        for (int end = start + 1; end <= s.length(); end++) {
            String word = s.substring(start, end);
            if (wordSet.contains(word)) {
                List<String> subResults = backtrack(s, end, wordSet, memo);
                for (String subResult : subResults) {
                    result.add(word + (subResult.isEmpty() ? "" : " " + subResult));
                }
            }
        }

        // 将计算结果存储在记忆化存储中
        memo.put(start, result);
        return result;
    }
}
相关推荐
CoderYanger3 分钟前
递归、搜索与回溯-综合练习:28.不同路径Ⅲ
java·算法·leetcode·深度优先·1024程序员节
鱼丸花生3 分钟前
Java 数组详解
java
用户84913717547164 分钟前
Tomcat 为什么要“造反”?深度解析 Java 类加载机制的“守”与“破”
java·jvm
jiayong2310 分钟前
Elasticsearch Java 开发完全指南
java·大数据·elasticsearch
321茄子11 分钟前
MySQL 事务隔离性及锁
java·数据库·mysql
杀死那个蝈坦14 分钟前
UV 统计(独立访客统计)
java·jvm·spring·kafka·tomcat·maven
带刺的坐椅16 分钟前
Solon AI 开发学习7 - chat - 四种消息类型及提示语增强
java·ai·llm·solon
济宁雪人17 分钟前
Java安全基础——序列化/反序列化
java·开发语言
1***Q78417 分钟前
后端在微服务中的服务路由
java·数据库·微服务
q***017718 分钟前
Java进阶--IO流
java·开发语言