77. 组合
易错点:
- startNumber 不是索引,就是实际的数,因为本题是 [1, n],是从 1 开始的。
- 回溯时是 i + 1,不是startNumber + 1。
未剪枝;全局变量:
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
backtrack(n, k, 1);
return result;
}
public void backtrack(int n, int k, int startNumber) {
if (path.size() == k) {
result.add(new ArrayList<>(path));
return;
}
for (int i = startNumber; i <= n; i++) {
path.add(i);
backtrack(n, k, i + 1); // 注意这里是 i + 1,不是 startNumber + 1
path.remove(path.size() - 1);
}
}
}
未剪枝;局部变量 -> 参数增多:
class Solution {
public List<List<Integer>> combine(int n, int k) {
List<Integer> path = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();
backtrack(n, k, 1, path, result);
return result;
}
public void backtrack(int n, int k, int startNumber, List<Integer> path, List<List<Integer>> result) {
if (path.size() == k) {
result.add(new ArrayList<>(path));
return;
}
for (int i = startNumber; i <= n; i++) {
path.add(i);
backtrack(n, k, i + 1, path, result); // 注意这里是 i + 1,不是 startNumber + 1
path.remove(path.size() - 1);
}
}
}
剪枝:
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
backtrack(n, k, 1);
return result;
}
public void backtrack(int n, int k, int startNumber) {
if (path.size() == k) {
result.add(new ArrayList<>(path));
return;
}
for (int i = startNumber; i <= n; i++) {
// 剪枝
if (path.size() + n - i + 1 < k) {
break;
}
path.add(i);
backtrack(n, k, i + 1); // 注意这里是 i + 1,不是 startNumber + 1
path.remove(path.size() - 1);
}
}
}
216. 组合总和 III
初始:有参数 sum
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
backtrack(k, n, 0, 1);
return result;
}
public void backtrack(int k, int n, int sum, int startNum) {
if (path.size() == k) {
if (n == sum) {
result.add(new ArrayList<>(path));
}
return;
}
for (int i = startNum; i <= 9; i++) {
sum += i;
path.add(i);
backtrack(k, n, sum, i + 1);
path.remove(path.size() - 1);
sum -= i;
}
}
}
优化:用 n 减减来判断是否等于 0
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
backtrack(k, n, 1);
return result;
}
public void backtrack(int k, int n, int startNum) {
if (path.size() == k) {
if (n == 0) {
result.add(new ArrayList<>(path));
}
return;
}
for (int i = startNum; i <= 9; i++) {
path.add(i);
backtrack(k, n - i, i + 1);
path.remove(path.size() - 1);
}
}
}
剪枝:剪去 n 过小和元素数量不够了
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
backtrack(k, n, 1);
return result;
}
public void backtrack(int k, int n, int startNum) {
// 剪枝 1
if (n < 0 || n > 45) { // 45 = 1 + 2 + ... + 9
return;
}
if (path.size() == k) {
if (n == 0) {
result.add(new ArrayList<>(path));
}
return;
}
for (int i = startNum; i <= 9; i++) {
if (path.size() + 9 - i + 1 < k) { // 剪枝 2
return;
}
path.add(i);
backtrack(k, n - i, i + 1);
path.remove(path.size() - 1);
}
}
}
17. 电话号码的字母组合
树的深度由数字个数确定
树的宽度由字母表长度确定
index:digits 的下标,即树的深度
letters.length():树的宽度

class Solution {
String[] digitToLetters = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
StringBuilder path = new StringBuilder();
List<String> result = new ArrayList<>();
public List<String> letterCombinations(String digits) {
backtrack(digits, 0);
return result;
}
public void backtrack(String digits, int index) { // index:digits 的下标,即树的深度
if (index == digits.length()) {
result.add(path.toString());
return;
}
int digit = digits.charAt(index) - '0';
String letters = digitToLetters[digit];
for (int i = 0; i < letters.length(); i++) { // letters.length():树的宽度
path.append(letters.charAt(i));
backtrack(digits, index + 1);
path.deleteCharAt(path.length() - 1);
}
}
}
评论区题解看到的迭代法,但只适用于每一层遍历都是从 0 开始的情况。还是回溯法更好更具有通用性。
public List<String> letterCombinations(String digits) {
List<String> result = new ArrayList<>();
int n = digits.length();
if (n == 0){
return result;
}
Map<Character, List<String>> map = new HashMap<>();
map.put('2', Arrays.asList("a","b","c"));
map.put('3', Arrays.asList("d","e","f"));
map.put('4', Arrays.asList("g","h","i"));
map.put('5', Arrays.asList("j","k","l"));
map.put('6', Arrays.asList("m","n","o"));
map.put('7', Arrays.asList("p","q","r","s"));
map.put('8', Arrays.asList("t","u","v"));
map.put('9', Arrays.asList("w","x","y","z"));
result = map.get(digits.charAt(0));
if (n == 1){
return result;
}
int i = 1;
while (i < n){
List<String> temp = new ArrayList<>();
for (String s1 : result) {
for (String s2 : map.get(digits.charAt(i))) {
temp.add(s1+s2);
}
}
result = temp;
i++;
}
return result;
}
回溯法总结
包含 5 类问题
- 组合问题:N个数里面按一定规则找出k个数的集合
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 棋盘问题:N皇后,解数独等等
模板:
void backtracking(参数) {
if (终止条件) {
收集结果;
return ;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
递归函数;
回溯操作;
}
return ;
}
回溯算法解决的任何问题都可以抽象成一棵树。

注:
组合、切割、排列和部分棋盘问题都是在叶子节点收集结果。
只有子集问题是在每个节点收集结果。
我对回溯法的理解:回溯法就是解决 Java 中没有办法可控 1~n 个 for 循环嵌套的问题,不然直接 n 个 for 循环暴力解决了。
1015

被折叠的 条评论
为什么被折叠?



