题目描述

给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中。返回所有这些可能的句子。

说明:

分隔时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。

示例 1:

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

示例 2:

输入:
s = “pineapplepenapple”
wordDict = [“apple”, “pen”, “applepen”, “pine”, “pineapple”]
输出:
[
  “pine apple pen apple”,
  “pineapple pen apple”,
  “pine applepen apple”
]
解释: 注意你可以重复使用字典中的单词。

示例 3:

输入:
s = “catsandog”
wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
输出:
[]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/word-break-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解法1 – Top-down

这道题是“139-单词拆分”的升级版本,在第139题中使用了Bottom-up和Top-down两种解法。但是这道题要求给出所有可能的组合,如果我们使用Bottom-up解法,会导致计算过程中产生大量不需要的中间结果。这也是Bottom-up的缺点,需要将所以子问题计算完成,才能够求解大规模的问题。而Top-down类似于按需计算,当发现有一个子问题没有被计算才会求解。

举一个例子,s=“aaab”,wordDict = [“a”]。这个例子很明显结果为空,因为无法用字典构成任何句子。如果使用Bottom-up方法,需要把子问题”a”、”aa”、”aaa”都算出来,才能计算”aaab”。最后发现这个字符串无法构成句子,那么前面的计算都白费了。 如果使用Top-down方法,将s=”aaab”拆分为”a”和”aab”、”aa”和”ab”、”aaa”和”b”,会发现没有任何一个拆分使得右半部分在字典中存在,就不会继续计算了。这就是使用Top-down方法的优点-避免没必要的计算。

题目139要求寻找字符串s能否用给定的词典组成,而这道题要求找出字符串s所有可能构成的句子,我们只需要对题目139的Top-down解法稍微进行修改就可以作为这道题目的答案。主要修改变量cache的数据结构,以及for循环的逻辑

第139题使用Map<String, Boolean> cache存储计算过的中间结果,这道题目要求计算组成的句子,所以我们需要把它改成Map<String, List<String>> cache,因为同一个前缀能够组成多个句子,所以需要使用列表存储。在第139题中,只要求判断s能被字典构成就可以退出循环。

 for (int splitIdx = 0; splitIdx < s.length(); splitIdx++) {
    if (wordBreak(s.substring(0, splitIdx), wordSet) && wordSet.contains(s.substring(splitIdx))) {
        cached = true;
        break; //找到s的一个解就可以退出
}

但是这道题目需要求得全部解,还需要把解存储起来,所以不能找到一个解就退出。首先枚举字符串s的分割点splitIdx,将字符串分割为前半部分s[0…splitIdx)和suffix = s[splitIdx…|s|)。如果前半部分能够组成句子,且后半部分suffix在字典中存在,那么将suffix追加到前半部分组成的句子列表。

for (int splitIdx = 0; splitIdx < s.length(); splitIdx++) {
    String suffix = s.substring(splitIdx);
    if (wordSet.contains(suffix)) {
        List<String> prefixList = wordBreak(s.substring(0, splitIdx), wordSet);
        for (String prefix : prefixList) {
            ans.add(prefix + " " + suffix);
        }
    }
}

例如s = “catsanddog“, wordDict = [“cat”, “cats”, “and”, “sand”, “dog”]。s的前缀”catsand“能够用字典构成句子{“cat sand”, “cats and”},且suffix = “dog”在字典中存在,那么s就可以构成句子{“cat sand”, “cats and”}.append(suffix) = {“cat sand suffix”, “cats and suffix”}。全部代码如下:

class Solution {
    private Map<String, List<String>> cache = new HashMap<>();

    public List<String> wordBreak(String s, List<String> wordDict) {
        return wordBreak(s, new HashSet<>(wordDict));
    }

    private List<String> wordBreak(String s, Set<String> wordSet) {
        List<String> ans = cache.get(s);
        if (ans != null)
            return ans;

        ans = new ArrayList<>();
        // 如果s本身就在字典中,需要加入cache,因为这是base case
        if (wordSet.contains(s))
            ans.add(s);
        // 对于s寻找一个分割点
        for (int splitIdx = 0; splitIdx < s.length(); splitIdx++) {
            String suffix = s.substring(splitIdx);
            if (wordSet.contains(suffix)) {
                // prefixList存储了前缀组成句子的列表,如果前缀不能组成句子则列表为空
                List<String> prefixList = wordBreak(s.substring(0, splitIdx), wordSet);
                for (String prefix : prefixList) {
                    ans.add(prefix + " " + suffix);
                }
            }
        }
        // 当计算完成,才能将answer存入cache中。
        cache.put(s, ans);
        return ans;
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *