String 构造式 DFS + Backtracking
然而 “字符串构造式 DFS + backtracking”,还有最简单直接又好分析时间复杂度的做法,就是从 String 中每个字符的角度出发,去考虑 “拿 / 不拿”。
StringBuilder 的构造式 DFS + Backtracking,对 “加 / 不加” 的先后顺序是敏感的,下面两题中,同样的代码,都是
原因在于,这个模板是 “带着当前考虑元素” 的 DFS,在当前函数尾部回溯。当两个 dfs 同处一层,而同层 dfs 只在最尾部才回溯的时候,如果先跑添加的分支,会在后面执行不添加分支时候出错。
否则就一次 dfs 后面跟着一次 sb.setLength(),也可以。
然而 “字符串构造式 DFS + backtracking”,还有最简单直接又好分析时间复杂度的做法,就是从 String 中每个字符的角度出发,去考虑 “拿 / 不拿”。
public class Solution {
public List<String> generateAbbreviations(String word) {
List<String> list = new ArrayList<>();
dfs(list, new StringBuilder(), word.toCharArray(), 0, 0);
return list;
}
private void dfs(List<String> list, StringBuilder sb, char[] word, int index, int curNum){
int len = sb.length();
if(index == word.length){
if(curNum != 0) sb.append(curNum);
list.add(sb.toString());
} else {
// Don't add, merge to current number
dfs(list, sb, word, index + 1, curNum + 1);
// Add current char
if(curNum != 0) sb.append(curNum);
dfs(list, sb.append(word[index]), word, index + 1, 0);
}
sb.setLength(len);
}
}这题的结构就和 Different ways to add parenthese 不一样,括号之间的相对位置是非常重要的,而且有连续性,不能像那题一样直接在操作符上建一个 expression tree 出来,divide & conquer.
只有一个非法括号的情况:
有两个非法括号的情况:
觉得顺着这个思路越想细节越多。。于是参考了下论坛的 DFS 思路,在所有 DFS 解法中,我最喜欢这个,非常简洁易懂,而且不依赖什么 trick,便于和面试官交流。
这种解法的主要优点是好理解,空间上因为只用了一个 StringBuilder 非常经济,相比 BFS 速度和空间上都优异很多。如果说进一步优化的空间在哪,那就是对于 “重复状态” 的缓存与记忆化搜素还有提高的空间。
于是很 naive 的尝试了下加入 Set<> 来记录已经访问过的 StringBuilder 状态试图剪枝,然而很容易就出了 “没有任何返回结果” 的 bug ,其原因是,这个 dfs + backtracking 的代码本来就是在一个神似 binary tree 的结构上做一个 dfs 穷举而已,并不是 top-down recursion 那种子问题覆盖的结构,所以不适合用记忆化搜索解决。非要用的话至少 dfs 的返回类型就不能是 void ,至少也得是个 List<> 或者 Set<> 之类的 “子问题结果” 嘛。
而这题里,用 divide & conquer 无可避免地遇到符号优先级问题。于是看了一圈 LC 论坛,大家的做法其实都是 DFS + backtracking ..

所以说,珍爱生命,有乘法就远离 divide & conquer 吧。
一个思路不错的帖子,这种做法就不用考虑 join了,只要做乘法的时候看它的前一个元素就行;
流程:
Last updated