Q:给定一个字符串s和一组单词dict,在s中添加空格将s变成一个句子,使得句子中的每一个单词都是dict中的单词
返回所有可能的结果
例如:给定的字符串s ="catsanddog",
dict =["cat", "cats", "and", "sand", "dog"].
返回的结果为["cats and dog", "cat sand dog"].
A:引用:https://www.cnblogs.com/grandyang/p/4576240.html
像这种返回结果要列举所有情况的题,十有八九都是要用递归来做的。
如果就给你一个s和wordDict,不看Output的内容,你会怎么找出结果。
比如对于例子,博主可能会先扫一遍wordDict数组,看有没有单词可以当s的开头,那么我们可以发现cat和cats都可以。
比如我们先选了cat,那么此时s就变成了 "sanddog",我们再在数组里找单词,发现了sand可以,最后剩一个dog,也在数组中,于是一个结果就出来了。
然后回到开头选cats的话,那么此时s就变成了 "anddog",我们再在数组里找单词,发现了and可以,最后剩一个dog,也在数组中,于是另一个结果也就出来了。
那么这个查询的方法很适合用递归来实现,因为s改变后,查询的机制并不变,很适合调用递归函数。再者,我们要明确的是,如果不用记忆数组做减少重复计算的优化,那么递归方法跟brute force没什么区别,大概率无法通过OJ。所以我们要避免重复计算。
如何避免呢,还是看上面的分析,如果当s变成 "sanddog"的时候,那么此时我们知道其可以拆分成sand和dog,当某个时候如果我们又遇到了这个 "sanddog"的时候,我们难道还需要再调用递归算一遍吗,当然不希望啦,所以我们要将这个中间结果保存起来,由于我们必须要同时保存s和其所有的拆分的字符串,那么可以使用一个HashMap,来建立二者之间的映射,那么在递归函数中,我们首先检测当前s是否已经有映射,有的话直接返回即可。
如果s为空了,我们如何处理呢,题目中说了给定的s不会为空,但是我们递归函数处理时s是会变空的,这时候我们是直接返回空集吗,这里有个小trick,我们其实放一个空字符串返回,为啥要这么做呢?我们观察题目中的Output,发现单词之间是有空格,而最后一个单词后面没有空格,所以这个空字符串就起到了标记当前单词是最后一个,那么我们就不要再加空格了。
接着往下看,我们遍历wordDict数组,如果某个单词是s字符串中的开头单词的话,我们对后面部分调用递归函数,将结果保存到list中,然后遍历里面的所有字符串,和当前的单词拼接起来,这里就用到了我们前面说的trick。for循环结束后,记得返回结果array之前建立其和s之间的映射,方便下次使用。
public static ArrayList<String> wordBreak(String s, Set<String> dict) {
HashMap<String, ArrayList<String>> map = new HashMap<>();
return dfs(s, dict, map);
}
public static ArrayList<String> dfs(String s, Set<String> dict, HashMap<String, ArrayList<String>> map) {
//避免重复检索
if (map.containsKey(s))
return map.get(s);
ArrayList<String> array = new ArrayList<>();
//如果为空,返回一个空string
if (s.isEmpty()) {
array.add("");
return array;
}
for (String sub : dict) {
//以dict中某一个substring为开头的话(多种开头可能性)
if (s.startsWith(sub)) {
//把后面的string放入递归,返回后方string的各种带空格的搭配
ArrayList<String> list = dfs(s.substring(sub.length()), dict, map);
//把开头加入
for (String str : list) {
array.add(sub + (str.isEmpty() ? "" : " ") + str);
}
}
}
//把当前string和其剪开的各种搭配放入map中
map.put(s, array);
return array;
}
P.S.这道题放牛客就很坑,牛客非要按照顺序来是什么鬼……