力扣第140题-单词拆分 II

83 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情

前言

力扣第140题 单词拆分 II 如下所示:

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

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

示例 1:

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

一、思路

这一题与前面一题 力扣第139题-单词拆分 基本是一样的,都是用单词字典 wordDict 中的单词来拼凑出目标字符串。但是有一点不同:这一题需要知道由单词字典 wordDict 中单词拼凑出字符串 s 的所有组合

虽然题目中要求返回的结果列表中是以空格 隔开的句子,我们在分析的过程过程中可以先不考虑返回的形式,重要的是记录单词拼凑组合的内容

我们可以在第一题的基础上来看这一题,假设我们已经通过动态规划知道了子串 s[0 ~ i] 是否可以由单词字典中的单词组成,我们可以目标字符串的后面开始找到单词字典中的每个单词,假设 s[j ~ len] 为单词字典 wordDict 中的单词。那么我么后续可以继续向前寻找是否有单词存在,直到单词组合可以覆盖整个字符串。

综上所述,大致的步骤如下所示:

  1. 使用动态规划,找出子字符串 s[0 ~ i] 是否可以由单词组合成
  2. 从字符串的后面向前面找匹配的单词,依次向前,直到可以完整覆盖所有的字符串

图解算法

此处以示例1中的 s = "catsanddog", wordDict = ["cat","cats","and","sand","dog"] 作为例子来分析

  1. 动态规划初始化子串是否可以由单词组成

该部分的动态规划部分与上一题完全一致,我这里使用的与上一题完全相同的实现代码

初始后的结果如下所示:

image.png

  1. 从字符串后面向前找

我们很轻易就能找到 dog 可以匹配,且此时剩余的字符串 catsand 也可以由单词组合成,即 dp[6] == true

image.png

  1. 继续向前找

我们很轻易就能找到 sand 可以匹配,且此时剩余的字符串 cat 也可以由单词组合成,即 dp[2] == true

image.png

  1. 第一组正确结果

同理可以匹配 cat,此时的组合将整个字符串覆盖了,故得到了第一组正确的结果。

image.png

其他的结果集获取的过程同上所示。

二、实现

实现代码

实现代码与思路中保持一致

List<String> ret = new ArrayList<>();

public List<String> wordBreak(String s, List<String> wordDict) {
    // 动态规划求子串是否有解
    int len = s.length();
    Set<String> wordDictSet = new HashSet(wordDict);
    boolean[] dp = new boolean[len+1];
    dp[0] = true;   // 空字符串
    for (int i=1; i <= len; i++) {
        for (int j=0; j < i; j++) {
            if (dp[j] && wordDictSet.contains(s.substring(j, i))) {
                dp[i] = true;   // 有 true 就立刻去初始化下一个dp
                break;
            }
        }
    }
    // 如果字符串不能匹配的话,则直接返回
    if (!dp[len])
        return ret;
    // 递归
    Deque<String> stack = new LinkedList<>();
    dfs(s, len, wordDictSet, dp, stack);
    return ret;
}

public void dfs(String s, int end, Set<String> wordDictSet, boolean[] dp, Deque<String> path){
    if (end == 0){
        // 收集结果
        ret.add(String.join(" ", path));
    }
    // 从后往前走
    for (String word : wordDictSet){
        if (s.substring(0, end).endsWith(word) && dp[end - word.length()]){
            path.push(word);
            dfs(s, end-word.length(), wordDictSet, dp, path);
            path.pop();
        }
    }
}

测试代码

public static void main(String[] args) {
    String s = "catsanddog";
    List<String> wordDict = Arrays.asList("cat","cats","and","sand","dog");
    new Number140().wordBreak(s, wordDict);
}

结果

image.png

三、总结

感谢看到最后,非常荣幸能够帮助到你~♥

如果你觉得我写的还不错的话,不妨给我点个赞吧!如有疑问,也可评论区见~