题目描述(困难难度)

139 题 的升级版。给一个字符串,和一些单词,找出由这些单词组成该字符串的所有可能,每个单词可以用多次,也可以不用。

完全按照 的思路做了,大家可以先过去看一下。

解法一 动态规划

先考虑 最后一个解法,动态规划,看起来也比较简单粗暴。

这里修改的话,我们只需要用 dp[i] 表示字符串 s[0,i)wordDict 构成的所有情况。

总体思想还是和之前一样的。

  1. X X X X X X
  2. ^ ^ ^
  3. 0 j i
  4. 先判断 j i 的字符串在没在 wordDict
  5. 然后把 0 j 的字符串由 wordDict 构成所有情况后边加空格再加上 j i 的字符串即可

遗憾的是,熟悉的问题又来了。

由于 s 中的 b 字母在 wordDict 中并没有出现,所以其实我们并不需要做那么多循环,直接返回空列表即可。

和之前一样,所以我们可以先遍历一遍 swordDict ,从而确定 s 中的字符是否在 wordDict 中存在,如果不存在可以提前返回空列表。

  1. public List<String> wordBreak(String s, List<String> wordDict) {
  2. //提前进行一次判断
  3. HashSet<Character> set2 = new HashSet<>();
  4. for (int i = 0; i < wordDict.size(); i++) {
  5. String t = wordDict.get(i);
  6. for (int j = 0; j < t.length(); j++) {
  7. set2.add(t.charAt(j));
  8. }
  9. }
  10. for (int i = 0; i < s.length(); i++) {
  11. if (!set2.contains(s.charAt(i))) {
  12. return new ArrayList<>();
  13. }
  14. }
  15. HashSet<String> set = new HashSet<>();
  16. set.add(wordDict.get(i));
  17. }
  18. List<List<String>> dp = new ArrayList<>();
  19. temp.add("");
  20. dp.add(temp);
  21. for (int i = 1; i <= s.length(); i++) {
  22. temp = new ArrayList<>();
  23. for (int j = 0; j < i; j++) {
  24. if (set.contains(s.substring(j, i))) {
  25. for (int k = 0; k < dp.get(j).size(); k++) {
  26. String t = dp.get(j).get(k);
  27. if (t.equals("")) {
  28. temp.add(s.substring(j, i));
  29. } else {
  30. temp.add(t + " " + s.substring(j, i));
  31. }
  32. }
  33. }
  34. }
  35. dp.add(temp);
  36. }
  37. return dp.get(s.length());
  38. }

遗憾的是,刚刚那个例子通过了,又出现了新的问题。

140. Word Break II - 图1

再优化也想不到方法了,是我们的算法出问题了。因为 139 题中找到一个解以后就 break 了,而这里我们要考虑所有子串,所有的解,极端情况下时间复杂度达到了 O(n³)。还有一点致命的是,我们之前求的解最后可能并不需要。举个例子。

针对这个问题,我们可以优化一下,也就是下边的解法二

解法二

我们直接用递归的方法,先判断当前字符串在不在 wordDict 中,如果在的话就递归的去求剩余字符串的所有组成可能。此外,吸取之前的教训,直接使用 memoization 技术,将递归过程中求出来的解缓存起来,便于之后直接用。

  1. HashSet<String> set = new HashSet<>();
  2. for (int i = 0; i < wordDict.size(); i++) {
  3. set.add(wordDict.get(i));
  4. }
  5. return wordBreakHelper(s, set, new HashMap<String, List<String>>());
  6. private List<String> wordBreakHelper(String s, HashSet<String> set, HashMap<String, List<String>> map) {
  7. if (s.length() == 0) {
  8. return new ArrayList<>();
  9. }
  10. if (map.containsKey(s)) {
  11. return map.get(s);
  12. }
  13. List<String> res = new ArrayList<>();
  14. for (int j = 0; j < s.length(); j++) {
  15. //判断当前字符串是否存在
  16. if (set.contains(s.substring(j, s.length()))) {
  17. //空串的情况,直接加入
  18. if (j == 0) {
  19. res.add(s.substring(j, s.length()));
  20. } else {
  21. //递归得到剩余字符串的所有组成可能,然后和当前字符串分别用空格连起来加到结果中
  22. List<String> temp = wordBreakHelper(s.substring(0, j), set, map);
  23. for (int k = 0; k < temp.size(); k++) {
  24. String t = temp.get(k);
  25. res.add(t + " " + s.substring(j, s.length()));
  26. }
  27. }
  28. }
  29. }
  30. //缓存结果
  31. map.put(s, res);
  32. }

按理说其实可以直接就想到解法二,但受之前写的题的影响,这种分治的问题,都最终能转成动态规划,所以先写了动态规划的思路,想直接一步到位,没想到反而遇到了问题,很有意思,哈哈。原因就是你求子问题的代价很大,而动态规划就是要求所有的子问题。而解决最终问题的时候,一些子问题其实是没有必要的。

添加好友一起进步~

如果想系统的学习数据结构和算法,强烈推荐一个我之前学过的课程,可以点击 这里 查看详情