题目描述:
某财务部门结账时发现总金额不对头。很可能是从明细上漏掉了某1笔或几笔。
如果已知明细账目清单,能通过编程找到漏掉的是哪1笔或几笔吗?
如果有多种可能,则输出所有可能的情况。
我们规定:用户输入的第一行是:有错的总金额。
接下来是一个整数n,表示下面将要输入的明细账目的条数。
再接下来是n行整数,分别表示每笔账目的金额。
要求程序输出:所有可能漏掉的金额组合。每个情况1行。金额按照从小到大排列,中间用空格分开。
比如:
用户输入:
6
5
3
2
4
3
1
表明:有错的总金额是6;明细共有5笔。
此时,程序应该输出:
1 3 3
1 2 4
3 4
为了方便,不妨假设所有的金额都是整数;每笔金额不超过1000,金额的明细条数不超过100。
本体使用的是递归思想,有几个关键点和难点;
首先说关键点:
1.首先我们要读懂题目,要求我们找出超过n元以外的金额的账单,以示例来说,就是要求err_money=6,那么我们就要找到几笔账单(这些账单金额的和为6元),然后依次输出剩下的几笔账单的金额。所以本题两种思路,第一种是dfs找出所有和为6的结果,输出数组剩下的元素;第二种是先算出所有账单金额的总和,减去err_money(也就是一开始输入的6),得出剩下的金额总和,再用dfs找出所欲和为剩余金额的方案,以题目的示例,就是找出和为 (3+2+4+3+1)- 6 的所有方案。本次博客先介绍前者的方法,后者的方法下次再写。
2.递归的思想:这里简单的说就是,找到一个变量或元素,决定选他或者不选他,这样就会产生两种情况,然后递归,记得回溯。
例如:本题中,我们选择的数组num[] 3 2 4 3 1 中。假设我已经我选了num[0],那么对于num[1]来说,我选择了他并于num[0]相加,将会产生第一种情况,得出的和为5;如果不选择他与num[1]相加,那么将会产生第二种情况,就是目前的和为3。
3.要寻找到递归函数的几个参数,首先需要想到有err_money的参数(也就是错误的金额的和),以此来确定是否到达要取的值,然后要想到,我如何才能让我取的几个值的和err_money比较,那我就需要一个current_money的变量与其比较(也就是时刻记录金额的和),而这个current_money的变量我如何为他赋值嘞?因为题目可以认为是一个数组形式(下一篇文章我会介绍将其当成一个集合的形式来写),那么我可以在此产生一个vis[]的数组来依次标记数组的元素,vis数组默认全是0,也就是未访问的意思(vis是visit的缩写,代表访问数组,这个标记法应该是dfs的基础标配了吧,具体可以点击这里),所以认为的状态就是,vis[i]是0,就未被访问(没有加入到current_money中),对应上面的第一种情况,如果被访问了,就被加入变量了,视为第二种方案。因为使用dfs标记法需要回溯,所以每一次要记得将vis[i]重新置为0。所以要加入数组num以及他的元素下标step,以此来不断的找到数组的各个元素,然后分为很多种情况。
难点:
1.题目中要求输出所有可能漏掉的金额组合。金额按照从小到大排列。对于大小排列,可以用Arrays.sort()来解决
所以我们要注意是漏掉的组合,而不是所有情况,对于3 4 和 4 3 其实是算一种的,所以你跑起来可能会发现有两行可能是一样的,然后目前我想出来的方法使用StringBuffer来解决这个问题,但下面的代码没有写上,将会在下一个博客中写。
2.对于dfs函数的出口也很重要,出口条件位置放的不对,可能会使输出乱七八糟,也可以试着遵循一个原则,对于step参数(就是会不断+1递归的那个,本类型也可以粗略理解为数组下标),最好就放在最下面,有些特别情况例外,因为对于数组的判断一般来说是最后的。
import java.util.Arrays;
import java.util.Scanner;
public class 财务部门结账 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int err_money = sc.nextInt();//err_money 指一开始错误的财务总和
int current_money = 0;//current_money 指 每一步加或不加的财务总和
int n = sc.nextInt();//n 指账目数量
int num [] = new int[n];//数组内的元素 指每个账目的具体金额
int vis[] = new int[n];//vis数组 用来记录当前的元素是否被访问(即是否被加在current_money中)
int step = 0;//step 指num数组中的元素下标
for(int i = 0 ; i < num.length ; i++) {
num[i] = sc.nextInt();
}
Arrays.sort(num);
f(err_money,num,step, current_money,vis);
//起始状况:f(err_money,num,0,0,0);
}
public static void f(int err_money, int[] num, int step, int current_money, int[] vis) {
if(current_money > err_money) return;
if(current_money == err_money) {
for(int i = 0 ; i < num.length ; i ++) {
if(vis[i] == 0) {
System.out.print(num[i]+" ");
}
}
System.out.println();
return;
}
if(step >= num.length) return;
vis[step] = 0;
f(err_money,num,step+1, current_money,vis);
vis[step] = 1;
f(err_money,num,step+1, current_money+num[step],vis);
vis[step] = 0;//回溯
}
}