[LeetCode] 139. 单词拆分 ☆☆☆(动态规划 回溯)

139. 单词拆分

描述

给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

说明:

拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
示例 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

解析

初次思路(错误)

看题目,就是依次比较单独是否在列表中,在的话,start = end,继续比较。上述示例都可以通过。

但是:"goalspecial"    ["go","goal","goals","special"] 这样的示例不通过,看这个例子。明显不是这个思路了。

回溯

用递归和回溯。为了找到解,我们可以检查字典单词中每一个单词的可能前缀,如果在字典中出现过,那么去掉这个前缀后剩余部分回归调用。同时,如果某次函数调用中发现整个字符串都已经被拆分且在字典中出现过了,函数就返回 true 。

回溯优化

在先前的方法中,我们看到许多函数调用都是冗余的,也就是我们会对相同的字符串调用多次回溯函数。为了避免这种情况,我们可以使用记忆化的方法,其中一个 memomemo 数组会被用来保存子问题的结果。每当访问到已经访问过的后缀串,直接用 memomemo 数组中的值返回而不需要继续调用函数。

通过记忆化,许多冗余的子问题可以极大被优化,回溯树得到了剪枝,因此极大减小了时间复杂度。

动态规划

这个方法的想法是对于给定的字符串(s)可以被拆分成子问题 s1 和 s2 。如果这些子问题都可以独立地被拆分成符合要求的子问题,那么整个问题 s 也可以满足。也就是,如果 "catsanddog" 可以拆分成两个子字符串 "catsand" 和 "dog" 。子问题 "catsand" 可以进一步拆分成 "cats" 和 "and" ,这两个独立的部分都是字典的一部分,所以 "catsand" 满足题意条件,再往前, "catsand" 和 "}dog" 也分别满足条件,所以整个字符串 "catsanddog" 也满足条件。

现在,我们考虑 dp 数组求解的过程。我们使用 n+1大小数组的dp ,其中 n 是给定字符串的长度。我们也使用 2 个下标指针 i 和 j ,其中 i 是当前字符串从头开始的子字符串(s‘)的长度, j 是当前子字符串(s‘)的拆分位置,拆分成 s‘(0,j)和 s‘(j+1,i)

为了求出 dp 数组,我们初始化dp[0] 为true ,这是因为空字符串总是字典的一部分。 dp 数组剩余的元素都初始化为 false

我们用下标 i 来考虑所有从当前字符串开始的可能的子字符串。对于每一个子字符串,我们通过下标 j 将它拆分成 s1‘ 和 s2‘ (注意 i 现在指向 s2‘ 的结尾)。为了将dp[i] 数组求出来,我们依次检查每个 dp[j] 是否为 true,也就是子字符串 s1‘ 是否满足题目要求。如果满足,我们接下来检查 s2‘ 是否在字典中。如果包含,我们接下来检查 s2‘ 是否在字典中,如果两个字符串都满足要求,我们让 dp[i] 为 true ,否则令其为false

代码

初次思路(错误)

public boolean wordBreak(String s, List<String> wordDict) {
        Map<String, Boolean> map = new HashMap(s.length());
        wordDict.forEach(word -> map.put(word, false));
        int start = 0;
        for (int end = 1; end <= s.length(); end++) {
            String key = s.substring(start, end);
            if (Objects.equals(false, map.get(key))) {
                map.put(key, true);
                start = end;
            }
        }
        System.out.println(start);
        return start == s.length() || map.containsKey(s.substring(start, s.length()));
    }

回溯

public boolean wordBreak(String s, List<String> wordDict) {
        return wordBreakH(s, new HashSet(wordDict), 0);
    }
    public boolean wordBreakH(String s, Set<String> wordDict, int start) {
        if (start == s.length()) {
            return true;
        }
        for (int end = start + 1; end <= s.length(); end++) {
            if (wordDict.contains(s.substring(start, end)) && wordBreakH(s, wordDict, end)) {
                return true;
            }
        }
        return false;
    }

回溯优化

public boolean wordBreak(String s, List<String> wordDict) {
        return wordBreak(s, new HashSet(wordDict), 0, new Boolean[s.length()]);
    }

    public boolean wordBreak(String s, Set<String> wordDict, int start, Boolean[] memo) {
        if (start == s.length()) {
            return true;
        }
        if (memo[start] != null) {
            return memo[start];
        }
        for (int end = start + 1; end <= s.length(); end++) {
            if (wordDict.contains(s.substring(start, end)) && wordBreak(s, wordDict, end, memo)) {
                return memo[start] = true;
            }
        }
        return memo[start] = false;
    }

动态规划

public boolean wordBreak(String s, List<String> wordDict) {
        Set<String> wordDictSet = new HashSet(wordDict);
        boolean[] dp = new boolean[s.length() + 1];
        dp[0] = true;
        for (int i = 1; i <= s.length(); i++) {
            for (int j = 0; j < i; j++) {
                if (dp[j] && wordDictSet.contains(s.substring(j, i))) {
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[s.length()];
    }

原文地址:https://www.cnblogs.com/fanguangdexiaoyuer/p/12193577.html

时间: 2024-08-30 02:25:47

[LeetCode] 139. 单词拆分 ☆☆☆(动态规划 回溯)的相关文章

leetcode 139.单词拆分

给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词. 说明: 拆分时可以重复使用字典中的单词. 你可以假设字典中没有重复的单词. 示例 1: 输入: s = "leetcode", wordDict = ["leet", "code"] 输出: true 解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet cod

139. 单词拆分

参考: https://leetcode-cn.com/problems/word-break/solution/dan-ci-chai-fen-by-leetcode/ 暴力递归 超时,时间复杂度:O(N^N), 空间复杂度O(N): 递归的深度 public boolean wordBreak(String s, List<String> wordDict) { return wordBreakSet(s, new HashSet<>(wordDict)); } private

leetcode 343. 整数拆分:动态规划(c++)

leetcode 343. 整数拆分 分析 状态表示: · dp[i] 表示整数 i 拆分乘积的最大值. 转移方程: · 对于每个数字 i 都进行一遍循环,计算 (i - j) * j,(j <= i - 1),并求其与 dp[i],dp[i - j] * j 的最大值,即:dp[i] = max(dp[i],(i - j) * j,dp[i - j] * j) · 与 dp[i - j] * j 比较是因为 i - j 可能小于 i - j 拆分的乘积. 边界: · 输入的整数 n 大于等于

【LeetCode-面试算法经典-Java实现】【139-Word Break(单词拆分)】

[139-Word Break(单词拆分)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated sequence of one or more dictionary words. For example, given s = "leetcode", di

leetcode139.单词拆分

给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词. 思路分析: (1)比较容易想到的解法是,从字符串 s 开头处开始匹配,若匹配到字典里的字符串,则继续匹配剩下的字符串.这样递归下去,直到找到一个可行的序列或尝试所有的单词组合. 提交的时候发现算法超时,后来想想其实这种解法存在很大的冗余,比如同时存在 a, b, ab的字典.只需记录下字符串 s 中的序列可以被字典匹配到即可,其实就是动态规划的思想: (2)采用动

单词拆分II

#单词拆分II#给一字串s和单词的字典dict,在字串中增加空格来构建一个句子,并且所有单词都来自字典.#返回所有有可能的句子.#EXAMPLE:#给一字串lintcode,字典为["de", "ding", "co", "code", "lint"]#结果为["lint code", "lint co de"]. class Solution:    "&

【leetcode-139】【回溯超时、动态规划】单词拆分

给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词. 说明: 拆分时可以重复使用字典中的单词.你可以假设字典中没有重复的单词.示例 1: 输入: s = "leetcode", wordDict = ["leet", "code"]输出: true解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code&qu

leetcode 139 word break (单词拆分)

一开始的错误答案与错误思路,幻想直接遍历得出答案: 1 class Solution { 2 public: 3 bool wordBreak(string s, vector<string>& wordDict) { 4 for(int i;i<s.size();i++){ 5 int step=0; 6 for(int j;j<wordDict.size();j++){ 7 if(s.substr(i,wordDict[j].size())==wordDict[j]){

leetcode 单词拆分 II java

题目: 给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中.返回所有这些可能的句子. 说明: 分隔时可以重复使用字典中的单词. 你可以假设字典中没有重复的单词. 示例 1: 输入: s = "catsanddog" wordDict = ["cat", "cats", "and", "sand", "dog&qu