摘要
本文主要介绍了LeetCode回溯算法的几个题目,包括216.组合总和III、17.电话号码的字母组合。
1、216.组合总和III
1.1 思路
-
递归函数的参数: 在递归函数中,我们需要传递以下参数:
k:表示还需要选择几个数字。n:表示目标和。start:表示当前考虑的数字的起始位置。path:表示当前的组合。
-
满足结束条件: 在递归函数的开头,首先判断是否满足结束条件。结束条件有两个:
- 当
k等于 0 且n等于 0 时,表示已经选择了k个数字且和为n,将当前的组合加入结果集。 当k不等于 0 且n小于 0 时,表示无法找到满足条件的组合,直接返回。
- 当
-
进行选择和递归: 在递归函数的主体部分,我们需要考虑两种情况:
- 选择当前数字
i,将其添加到当前组合path中,然后递归调用函数,传递参数k - 1、n - i和i + 1。 不选择当前数字i,直接递归调用函数,传递参数k和n。
- 选择当前数字
-
撤销选择: 在每一次递归返回后,需要撤销选择,即将当前数字从组合
path中移除,以便考虑下一个数字的选择。 -
回溯搜索: 通过不断地进行选择和递归,回溯算法会搜索所有可能的组合。
1、可以剪枝优化吗?
第一步剪枝: 如果在递归的某一层中,发现
n - i小于 0,这意味着在当前状态下,无论后面选择哪些数字,都不可能满足和为n的条件。因此,可以直接返回,不再继续向下递归,从而减少不必要的计算。第二步剪枝:
i的取值范围是[start, 9 - k + 1],确实是为了确保每次选择的数字不会超出范围,以满足问题的要求。举例来说,当k = 3, n = 7时,i可以取 7,但不能取 8,因为数字范围只允许 1 到 9。
2、216.组合总和III与77.组合剪枝的区别?
在第 216 题中,你提到
i的取值范围是[start, 9 - k + 1],因为k在递归中会减小,所以要考虑可选数字的范围应该是[start, 9 - k + 1]在第 77 题中,你提到
i的取值范围是[start, (n - k + 1) + linked.size()],因为k在递归中不变,所以要考虑可选数字的范围是[start, (n - k + 1) + linked.size()]
1.2 代码
public List<List<Integer>> combinationSum3(int k, int n) {
List<List<Integer>> list = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
doCombinationSum3(list, path, k, n, 1);
return list;
}
public void doCombinationSum3(List<List<Integer>> list, LinkedList<Integer> path, int k, int n,
int start) {
if(k == 0) {
if(n == 0) {
list.add(new ArrayList<>(path));
}
return;
}
for(int i=start; i<=9-k+1; i++) {
if(n - i < 0) {
continue;
}
path.add(i);
doCombinationSum3(list, path, k-1, n-i, i+1);
path.removeLast();
}
}
2、17.电话号码的字母组合
2.1 思路
- 遍历输入的电话号码数字串,对每个数字找到其对应的字母集合,然后递归地生成所有可能的字母组合
- 递归的终止条件是当前组合的长度等于输入数字串的长度,此时将组合加入结果集
- 在递归的过程中,需要不断地选择一个字母,加入组合,然后继续递归下一层,最后撤销选择以尝试下一个字母
1、与其他组合问题的区别?
特定输入和字母映射: 在电话号码的字母组合问题中,输入是一个包含数字的字符串,例如 "23",而不是一组数字数组。此外,电话号码的字母映射是固定的,每个数字都对应了一组字母,例如 '2' 对应 "abc",'3' 对应 "def",以此类推。其他组合问题可能涉及更一般化的输入和自定义的字母映射。
2.2 代码
public List<String> letterCombinations(String digits) {
List<String> list = new ArrayList<>();
if (digits == null || digits.length() == 0) {
return list;
}
// 定义数字到字母的映射
String[] arr = new String[]{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
// 用于暂存每一层的字母组合
StringBuilder builder = new StringBuilder();
doLetterCombinations(list, arr, digits, 0, builder);
return list;
}
public void doLetterCombinations(List<String> list, String[] arr, String digits, int start, StringBuilder builder) {
// 如果当前组合的长度等于输入数字串的长度,将其加入结果集
if(start == digits.length()) {
list.add(builder.toString());
return;
}
// 获取当前数字对应的字母集合
String letters = arr[digits.charAt(start)-'0'];
for(int i=0; i<letters.length(); i++) {
// 将该字母加入组合
builder.append(letters.charAt(i));
// 继续递归下一层
doLetterCombinations(list, arr, digits, start+1, builder);
// 撤销选择,以便尝试下一个字母
builder.deleteCharAt(builder.length()-1);
}
}