文章目录
- 1. 回溯法
- 1.1 组合
- 1.2 组合总和3
- 1.3 电话号码的字母组合
- 2.
1. 回溯法
经典问题:排列、组合、切割、子集、棋盘
void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {处理节点;backtracking(路径,选择列表); // 递归回溯,撤销处理结果}
}
1.1 组合
代码:
class Solution {List<List<Integer>> result = new ArrayList<>(); //记录返回的结果List<Integer> path = new LinkedList<>();public List<List<Integer>> combine(int n, int k) {backtracking(n, k, 1);return result;}public void backtracking(int n, int k, int startIndex){// 终止条件if(path.size() == k) {result.add(new ArrayList<>(path));return;}// for 循环处理每个节点,进行递归for(int i = startIndex; i <= n; i++){path.add(i);backtracking(n, k, i+1);// 回溯path.removeLast();}}
}
剪枝操作:
已经选择的元素个数:path.size();
还需要的元素个数为: k - path.size();
在集合n中至多要从该起始位置 : n - (k - path.size()) + 1,开始遍历
为什么有个+1呢,因为包括起始位置,我们要是一个左闭的集合。
代码:
class Solution {List<List<Integer>> result = new ArrayList<>();LinkedList<Integer> path = new LinkedList<>();public List<List<Integer>> combine(int n, int k) {combineHelper(n, k, 1);return result;}/*** 每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围,就是要靠startIndex* @param startIndex 用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。*/private void combineHelper(int n, int k, int startIndex){//终止条件if (path.size() == k){result.add(new ArrayList<>(path));return;}for (int i = startIndex; i <= n - (k - path.size()) + 1; i++){path.add(i);combineHelper(n, k, i + 1);path.removeLast();}}
}
1.2 组合总和3
思路:
树的深度(递归层数):k
树的宽度(for循环次数):9
每一层中,定义处理逻辑
class Solution {List<List<Integer>> result = new ArrayList<>(); // 记录返回的结果List<Integer> path = new ArrayList<>();public List<List<Integer>> combinationSum3(int k, int n) {backTracing(n, k, 1, 0);return result;}public void backTracing(int targetSum, int k, int startIndex, int sum){// 剪枝if (sum > targetSum){return;}if (path.size() == k) {if (sum == targetSum) {result.add(new ArrayList<>(path));}return;}// 递归,并剪枝for(int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) {path.add(i);sum += i;backTracing(targetSum, k, i+1, sum);// 回溯path.removeLast();sum -= i;}}
}
1.3 电话号码的字母组合
思路:输入两个数字两个for循环、3个数字三个for循环,输入n个数字用回溯,比如输入两个数字1和2,根节点先在1对应的字母里面取:for循环三次,每次取一个字母,然后递归取数字2对应的字母。下层就是在数字2的基础上,for循环,每次取一个数,递归下层,以此类推。
树的深度(递归层数) = 输入的数字个数
树的宽度(for循环次数) = 3
class Solution {//设置全局列表存储最后的结果List<String> result = new ArrayList<>();public List<String> letterCombinations(String digits) {if(digits == null || digits.length() == 0) {return result;}// 初始化对应的数字:为了直接对应2-9,新增加了两个无效的字符串""String[] numString = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};//迭代处理backTracking(digits, numString, 0);return result;}//每次迭代获取一个字符串,所以会涉及大量的字符串拼接,所以这里选择更为高效的 StringBuilderStringBuilder temp = new StringBuilder();// 根节点递归的是第一个数字,下一层节点递归的是第二个数字,以此类推// index 记录了当前是第几个数字public void backTracking(String digits,String[] numString, int index) {// 叶子节点收集,举例:index = 2,digits.length = 2,此时0和1位置数字处理完,现在是索引为2,可以收集了if(index == digits.length()) {result.add(temp.toString());return;}// str表示当前index对应的字符串String str = numString[digits.charAt(index) - '0'];// 举例 :'5' -> 5for (int i = 0; i < str.length(); i++) {temp.append(str.charAt(i));// 递归处理 下一层backTracking(digits, numString, index+1);// 回溯,temp.deleteCharAt(temp.length() - 1);}}
}