金額查錯—某財務部門結賬時發現總金額不對頭。很可能是從明細上漏掉了某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;//回溯
	}

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章