216. 组合总和III
如果把 组合问题理解了,本题就容易一些了。题目链接/文章讲解:代码随想录
视频讲解:和组合问题有啥区别?回溯算法如何剪枝?| LeetCode:216.组合总和III_哔哩哔哩_bilibili
重点:
1. 递归+回溯的模板
2. 回溯的剪枝
思路:
递归+回溯:
1. 确定参数及返回值
两个全局变量path和result,还要需要sum记录当前的和,startIndex记录从哪个开始
public void backtracking(int k, int n, int sum, int startIndex)
2. 确定终止条件
当path记录到了k个时,检查当前的sum是否满足条件
if (path.size() == k) { if (sum == n) { result.add(new ArrayList<>(path)); } return; }
3. 确定单层递归和回溯的逻辑
for循环控制每层的循环的1-9的哪一个,接着再递归下去就行
剪枝操作在函数开头,sum超过n了,直接return
还有for循环的限制也可以剪枝,因为如果后面的数并不能满足还差k几个的个数,就没必要for循环后面的数了
// path.size() 现在收集了几个 // k - path.size() 差路径所需要的个数几个 // 9 - (k - path.size()) + 1 至多直到哪个,加1是为了下标对齐,因为下标是1开始 // 想象一下,如果path.size()为2,k为5,我们还差3个没收集, // 9减去还差几个没收集再加1的就知道我们最多可以取到哪个位置 // 比如9-3+1=7,说明最多能到7,到了8我们就取不到3个的了,8只能取[8,9] for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) { sum += i; path.add(i); // 递归到下一层 backtracking(k, n, sum,i + 1); // 回溯到当前层以便进行另一个分枝的递归 sum -= i; path.remove(path.size() - 1); }
List<List<Integer>> result;
List<Integer> path;
public List<List<Integer>> combinationSum3(int k, int n) {
result = new ArrayList<>();
path = new ArrayList<>();
backtracking(k, n, 0,1);
return result;
}
public void backtracking(int k, int n, int sum, int startIndex) {
// 剪枝1
if (sum > n) {
return;
}
// 终止条件
if (path.size() == k) {
if (sum == n) {
result.add(new ArrayList<>(path));
}
return;
}
// 剪枝2
// path.size() 现在收集了几个
// k - path.size() 差路径所需要的个数几个
// 9 - (k - path.size()) + 1 至多直到哪个,加1是为了下标对齐,因为下标是1开始
// 想象一下,如果path.size()为2,k为5,我们还差3个没收集,
// 9减去还差几个没收集再加1的就知道我们最多可以取到哪个位置
// 比如9-3+1=7,说明最多能到7,到了8我们就取不到3个的了,8只能取[8,9]
for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) {
sum += i;
path.add(i);
// 递归到下一层
backtracking(k, n, sum,i + 1);
// 回溯到当前层以便进行另一个分枝的递归
sum -= i;
path.remove(path.size() - 1);
}
}
17. 电话号码的字母组合
本题大家刚开始做会有点难度,先自己思考20min,没思路就直接看题解。题目链接/文章讲解:代码随想录
视频讲解:还得用回溯算法!| LeetCode:17.电话号码的字母组合_哔哩哔哩_bilibili
重点:
1. Java对String频繁操作的时候,可以用StringBuilder
2. 递归+回溯
3. 数字和字母的映射
思路:
递归+回溯法:
1. 确定参数及返回值
用一个index来记录,当前for循环的digits中的哪一个
private void backtracking(String digits, int index)
2. 确定终止条件
最后一个digit也遍历完了
if (index == digits.length()) { result.add(s.toString()); return; }
3. 确定单层递归的逻辑
先获取到当前digit对应了哪些字母,再for循环这些字母。for循环中再递归调用下一个digit来达到嵌套N层for循环的目的,最后还要回溯到当前digit
// 获取当前digit对应哪些字母 int curDigit = digits.charAt(index) - '0'; String letter = letterMap[curDigit]; // for循环当前digit对应的那些字母 // 再用递归来嵌套下一个for循环 // 回溯回当前层时要删掉最后一个 for (int i = 0; i < letter.length(); i++) { s.append(letter.charAt(i)); backtracking(digits, index + 1); s.deleteCharAt(s.length() - 1); }
List<String> result;
// 当需要不断对String进行操作时,可以考虑用StringBuilder
StringBuilder s;
// 下标为0-9,对应的内容为各自的字母
String[] letterMap = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
public List<String> letterCombinations(String digits) {
result = new ArrayList<>();
if (digits == null || digits.length() == 0) {
return result;
}
s = new StringBuilder();
backtracking(digits, 0);
return result;
}
// 这里的index表示我们递归到了哪个digit了
private void backtracking(String digits, int index) {
// 终止条件
// 最后一个digit也全都遍历完了,所以index应该超出digit的范围
if (index == digits.length()) {
result.add(s.toString());
return;
}
// 获取当前digit对应哪些字母
int curDigit = digits.charAt(index) - '0';
String letter = letterMap[curDigit];
// for循环当前digit对应的那些字母
// 再用递归来嵌套下一个for循环
// 回溯回当前层时要删掉最后一个
for (int i = 0; i < letter.length(); i++) {
s.append(letter.charAt(i));
backtracking(digits, index + 1);
s.deleteCharAt(s.length() - 1);
}
}