分析:首先说明使用回溯,通过题眼“可能的所有组合,排列等”
对于回溯:
回溯本身就是暴力穷举,通过递归实现,这里不要纠结回溯和递归的关系,回溯就是一个套路通过递归实现而已。
主要有三个点:
- 路径:这三题其中对应的ad,((())),[1,2,3]就是一条完整的路径
- 决策:即每一个可能的选择,第一题,a的决策可以为def任一,(的决策可以为(或者),1的决策可以为2,3
- 结束条件:结束条件为通常为路径的长度,是固定的,比如这三题中对应的分别是2,6,3
下面的公式便是所有回溯解的思想
void recall(res, array, singleRes) {
// 结束条件
if (singleRes.length == array.length) {
res.add(singleRes);
} else {
// 这个for循环是指本次可选择的决策,理解这个很重要
for(i,i<array.length,i++) {
if(需要剪枝的情况) continue;
// 决策生成路径
singleRes.add(array[i]);
recall(res, array, singleRes);
// 撤销这一步,回到上一步
singleRes.remove(singleRes.length-1);
}
}
}
回溯是暴力破解的过程,最终是生成一颗树,撤销是为了回到上一步的状态(表现为树的后序遍历),需要注意的是可能存在剪枝的情况也就是有些情况我们不需要,比如第二题,我们的不能以)开头且)不能大于(的,第三题是不能有重复的情况,所以我们需要进行剪枝 17 不需要剪枝
public static List<String> letterCombinations(String digits) {
HashMap<Integer, String> map = new HashMap<>();
map.put(2, "abc");
map.put(3, "def");
map.put(4, "ghi");
map.put(5, "jkl");
map.put(6, "mno");
map.put(7, "pqrs");
map.put(8, "tuv");
map.put(9, "wxyz");
StringBuilder sb = new StringBuilder();
List<String> list = new LinkedList<>();
if ("".equals(digits)) {
return list;
}
return recall(digits,0,map,sb, list);
}
private static List<String> recall(String digits, int i, HashMap<Integer, String> map, StringBuilder sb,
List<String> list) {
if (sb.length() == digits.length()) {
list.add(sb.toString());
return list;
} else {
int num = Integer.parseInt(digits.charAt(i)+"");
String numStr = map.get(num);
for (int j = 0; j < numStr.length(); j++) {
sb.append(numStr.charAt(j));
recall(digits, i+1, map, sb, list);
sb.deleteCharAt(sb.length()-1);
}
}
return list;
}
22 需要剪枝,主要是()不能超过n数量以及)不能超过(
public static List<String> generateParenthesis(int n) {
List<String> list = new LinkedList<String>();
recall(list, n, new StringBuilder(), 0, 0);
return list;
}
private static void recall(List<String> list, int n, StringBuilder sb, int left, int right) {
if (sb.length() == n*2) {
list.add(sb.toString());
return;
} else {
for (int j = 0; j < 2; j++) {
String str;
if (j==0) {
str = "(";
left ++;
if (left > n) continue;
} else {
str = ")";
right ++;
if (right > left || right > n) continue;
}
sb.append(str);
recall(list, n, sb, left, right);
sb.deleteCharAt(sb.length()-1);
if (j==0) left--;
else right--;
}
}
}
46 存在剪枝的情况,不能有重复,注意结束时需要用新的list保存,否则浅拷贝会受影响
public static List<List<Integer>> permute(int[] nums) {
List<List<Integer>> list = new LinkedList<>();
List<Integer> res = new LinkedList<>();
recall(nums, list, res);
return list;
}
private static void recall(int[] nums, List<List<Integer>> list, List<Integer> res) {
if (res.size() == nums.length) {
list.add(new LinkedList<>(res));
return;
} else {
for (int i = 0; i < nums.length; i++) {
if (res.contains(nums[i])) continue;
res.add(nums[i]);
recall(nums, list, res);
res.remove(res.lastIndexOf(nums[i]));
}
}
}