学回溯的第二天,发现之前做过的一道洛谷的枚举题也可以用回溯法去解决,还是相当滴nice的。
先来看看leetcode上的这两道题
题目链接:216. 组合总和 III
思路就是比组合问题多了一个和为n的限制,大体还是可以按模板来的,代码如下:
class Solution {
List temp = new ArrayList<>();
List> result = new ArrayList<>();
public List> combinationSum3(int k, int n) {
backtracking(k, n, 1, 0);
return result;
}
// 1. 参数分别是:k个数、和为n、起始下标值(保证组合元素不重复)、求和计数器
public void backtracking(int k, int n, int startIndex, int sum) {
// 2. 循环终止条件,当temp中元素达到K个时,说明要停止往下递归了
if(temp.size() == k) {
if(sum == n) { // 如果和等于n,则收集结果
result.add(new ArrayList<>(temp));
return;
}else { // 如果不满足,那么什么都不干
return;
}
}
// 3. 单层循环逻辑
for(int i = startIndex; i <= 9; i++) { // 每次从传入的起始下标开始
sum += i; // 记录这一次的和
if(sum > n){ // 剪枝操作,如果和都大于n了,下面也就没有递归和加入集合temp集合的必要了
return;
}
temp.add(i); // 满足条件的直接加入temp中
backtracking(k, n, i + 1, sum); // 递归
sum -= i; // 回溯,计数器也要回溯
temp.remove(temp.size() - 1);
}
}
}
题目链接:P2089 烤鸡
做完这道题,我就想起了洛谷的这道P2089 烤鸡问题,这道题,比216.组合总和III 还更简单,它就是当n等于输入的n,k=10的限制要求,所以很容易写出类似的回溯代码:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
public class Main {
static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
static List temp = new ArrayList<>();
static List> result = new ArrayList<>();
static int count = 0; // 结果次数计数器
public static void main(String[] args) throws IOException {
in.nextToken();
int n = (int) in.nval;
backtracking(n,0); // 调用递归(回溯)方法
// 由于题目中说了,要先打印个数,再打印组合情况,所以要单独写个逻辑
if(count > 0) {
out.println(count);
for(List list : result) {
for(int s : list) {
out.print(s + " ");
}
out.println();
}
}else {
out.println(count);
}
out.close();
}
private static void backtracking(int n, int sum) {
if(temp.size() == 10) {
if(sum == n) {
count++;
result.add(new ArrayList<>(temp));
return;
}else {
return;
}
}
for(int i = 1; i <= 3; i++) {
sum += i;
if(sum > n) {// 剪枝
return;
}
temp.add(i);
backtracking(n,sum);
// 回溯
sum -= i;
temp.remove(temp.size() - 1);
}
}
}
可以看出来,和上面的216.组合总和III 问题没什么区别,就是多了一个题目要求的,需要先打印次数,再打印结果,但是这是洛谷上的题,还需要写Main函数,不能单单只写一个方法体,当然,下面还有一种这道题的解法,因为这道题只需要10个数字组合,并且数字只有1-3,所以可以直接10个for循环(类似于“百钱白鸡”问题的升级版),时间复杂度也只有O(3^10)而已,是个常数,可以AC的,下面是代码:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.math.BigInteger;
public class Main {
public static void main(String[] args) throws IOException {
StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
in.nextToken();
int count = 0;
int n = (int) in.nval;
if(n - 1 < 9 || n > 30) {
out.print(0);
}else {
for(int a = 1; a <= 3; a++) {
for(int b = 1; b <= 3; b++) {
for(int c = 1; c <= 3; c++) {
for(int d = 1; d <= 3; d++) {
for(int e = 1; e <= 3; e++) {
for(int f = 1; f <= 3; f++) {
for(int g = 1; g <= 3; g++) {
for(int h = 1; h <= 3; h++) {
for(int i = 1; i <= 3; i++) {
for(int j = 1; j <= 3; j++) {
if(a + b + c + d + e + f + g + h + i + j == n) {
count++;
}
}
}
}
}
}
}
}
}
}
}
out.println(count);
for(int a = 1; a <= 3; a++) {
for(int b = 1; b <= 3; b++) {
for(int c = 1; c <= 3; c++) {
for(int d = 1; d <= 3; d++) {
for(int e = 1; e <= 3; e++) {
for(int f = 1; f <= 3; f++) {
for(int g = 1; g <= 3; g++) {
for(int h = 1; h <= 3; h++) {
for(int i = 1; i <= 3; i++) {
for(int j = 1; j <= 3; j++) {
if(a + b + c + d + e + f + g + h + i + j == n) {
out.print(a + " ");
out.print(b + " ");
out.print(c + " ");
out.print(d + " ");
out.print(e + " ");
out.print(f + " ");
out.print(g + " ");
out.print(h + " ");
out.print(i + " ");
out.println(j);
}
}
}
}
}
}
}
}
}
}
}
}
out.close();
}
}
看这代码也是相当的炸裂了,哈哈哈哈,但是可以AC,还比回溯法AC更快!
题目链接:17. 电话号码的字母组合
一开始也是没想到用哈希表的思想来映射每一个数字对应的字符串,所以导致最后没做出来,这题关键就在于用一个String数组,通过数组下标作为值,映射一个字符串作为键,即:
String[] table = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
然后回溯,就是以传入的字符串大小为抽象树的深度,下面是代码:
class Solution {
List result = new ArrayList<>();
String[] table = {"", "", "abc", "def", "ghi", "jkl",
"mno", "pqrs", "tuv", "wxyz"};
public List letterCombinations(String digits) {
if(digits.isEmpty()) {
return result;
}
StringBuilder str = new StringBuilder();
backtracking(digits, 0, str);
return result;
}
// 1. k表示深度,用StringBuilder类型来做拼接操作更方便
private void backtracking(String digits, int k, StringBuilder str) {
if(str.length() == digits.length()) {
result.add(str.toString());
return;
}
int number = digits.charAt(k) - '0'; // 获取digits对应的第k个数字的数值
String numberStr = table[number]; // 根据数字,在表中找到相应数字(如,2对应的“abc”)
for(char ch : numberStr.toCharArray()) {
str.append(ch);
backtracking(digits, k + 1, str); // 注意这里层数参数时,不能传k++,因为k++会改变所有递归的k,
// k只记录这一个分支的层数,所以直接加1
// 回溯
str.deleteCharAt(str.length() - 1);
}
}
}
这题还有一个关键,就是在调用递归时,深度的参数不能传k++,即使是回溯了,也不行,k是随着层数越往下越深,而不是所有的k。