回溯法解决P2089 烤鸡问题太香啦,组合问题就用回溯法就对了!

学回溯的第二天,发现之前做过的一道洛谷的枚举题也可以用回溯法去解决,还是相当滴nice的。

先来看看leetcode上的这两道题

216.组合总和III 

题目链接:216. 组合总和 III

思路就是比组合问题多了一个和为n的限制,大体还是可以按模板来的,代码如下:

代码:

class Solution {
    List<Integer> temp = new ArrayList<>();
    List<List<Integer>> result = new ArrayList<>();
    public List<List<Integer>> 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 烤鸡

做完这道题,我就想起了洛谷的这道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<Integer> temp = new ArrayList<>();	
	static List<List<Integer>> 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<Integer> 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.电话号码的字母组合

题目链接:17. 电话号码的字母组合

一开始也是没想到用哈希表的思想来映射每一个数字对应的字符串,所以导致最后没做出来,这题关键就在于用一个String数组,通过数组下标作为值,映射一个字符串作为键,即:

String[] table = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};

然后回溯,就是以传入的字符串大小为抽象树的深度,下面是代码:

代码:

class Solution {
    List<String> result = new ArrayList<>();
    String[] table = {"", "", "abc", "def", "ghi", "jkl", 
			   "mno", "pqrs", "tuv", "wxyz"};
    public List<String> 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。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值