题目描述(困难难度)
的升级版。给一个字符串,和一些单词,找出由这些单词组成该字符串的所有可能,每个单词可以用多次,也可以不用。
完全按照 139 题 的思路做了,大家可以先过去看一下。
解法一 动态规划
先考虑 最后一个解法,动态规划,看起来也比较简单粗暴。
这里修改的话,我们只需要用 dp[i]
表示字符串 s[0,i)
由 wordDict
构成的所有情况。
总体思想还是和之前一样的。
X X X X X X
^ ^ ^
0 j i
先判断 j 到 i 的字符串在没在 wordDict 中
然后把 0 到 j 的字符串由 wordDict 构成所有情况后边加空格再加上 j 到 i 的字符串即可
遗憾的是,熟悉的问题又来了。
由于 s
中的 b
字母在 wordDict
中并没有出现,所以其实我们并不需要做那么多循环,直接返回空列表即可。
和之前一样,所以我们可以先遍历一遍 s
和 wordDict
,从而确定 s
中的字符是否在 wordDict
中存在,如果不存在可以提前返回空列表。
public List<String> wordBreak(String s, List<String> wordDict) {
//提前进行一次判断
HashSet<Character> set2 = new HashSet<>();
for (int i = 0; i < wordDict.size(); i++) {
String t = wordDict.get(i);
for (int j = 0; j < t.length(); j++) {
set2.add(t.charAt(j));
}
}
for (int i = 0; i < s.length(); i++) {
if (!set2.contains(s.charAt(i))) {
return new ArrayList<>();
}
}
HashSet<String> set = new HashSet<>();
set.add(wordDict.get(i));
}
List<List<String>> dp = new ArrayList<>();
temp.add("");
dp.add(temp);
for (int i = 1; i <= s.length(); i++) {
temp = new ArrayList<>();
for (int j = 0; j < i; j++) {
if (set.contains(s.substring(j, i))) {
for (int k = 0; k < dp.get(j).size(); k++) {
String t = dp.get(j).get(k);
if (t.equals("")) {
temp.add(s.substring(j, i));
} else {
temp.add(t + " " + s.substring(j, i));
}
}
}
}
dp.add(temp);
}
return dp.get(s.length());
}
遗憾的是,刚刚那个例子通过了,又出现了新的问题。
再优化也想不到方法了,是我们的算法出问题了。因为 139
题中找到一个解以后就 break
了,而这里我们要考虑所有子串,所有的解,极端情况下时间复杂度达到了 O(n³)
。还有一点致命的是,我们之前求的解最后可能并不需要。举个例子。
针对这个问题,我们可以优化一下,也就是下边的解法二
解法二
我们直接用递归的方法,先判断当前字符串在不在 wordDict
中,如果在的话就递归的去求剩余字符串的所有组成可能。此外,吸取之前的教训,直接使用 memoization
技术,将递归过程中求出来的解缓存起来,便于之后直接用。
HashSet<String> set = new HashSet<>();
for (int i = 0; i < wordDict.size(); i++) {
set.add(wordDict.get(i));
}
return wordBreakHelper(s, set, new HashMap<String, List<String>>());
private List<String> wordBreakHelper(String s, HashSet<String> set, HashMap<String, List<String>> map) {
if (s.length() == 0) {
return new ArrayList<>();
}
if (map.containsKey(s)) {
return map.get(s);
}
List<String> res = new ArrayList<>();
for (int j = 0; j < s.length(); j++) {
//判断当前字符串是否存在
if (set.contains(s.substring(j, s.length()))) {
//空串的情况,直接加入
if (j == 0) {
res.add(s.substring(j, s.length()));
} else {
//递归得到剩余字符串的所有组成可能,然后和当前字符串分别用空格连起来加到结果中
List<String> temp = wordBreakHelper(s.substring(0, j), set, map);
for (int k = 0; k < temp.size(); k++) {
String t = temp.get(k);
res.add(t + " " + s.substring(j, s.length()));
}
}
}
}
//缓存结果
map.put(s, res);
}
总
按理说其实可以直接就想到解法二,但受之前写的题的影响,这种分治的问题,都最终能转成动态规划,所以先写了动态规划的思路,想直接一步到位,没想到反而遇到了问题,很有意思,哈哈。原因就是你求子问题的代价很大,而动态规划就是要求所有的子问题。而解决最终问题的时候,一些子问题其实是没有必要的。
添加好友一起进步~
如果想系统的学习数据结构和算法,强烈推荐一个我之前学过的课程,可以点击 这里 查看详情