金额查错—某财务部门结账时发现总金额不对头。很可能是从明细上漏掉了某1笔或几笔

 题目描述:

某财务部门结账时发现总金额不对头。很可能是从明细上漏掉了某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;//回溯
	}

}

 

你可能感兴趣的:(算法)