1. 程式人生 > >美團點評筆試--拼湊錢幣

美團點評筆試--拼湊錢幣

題目

給你六種面額 1、5、10、20、50、100 元的紙幣,假設每種幣值的數量都足夠多,編寫程式求組成N元(N為0~10000的非負整數)的不同組合的個數。

  • 輸入
    輸入包括一個整數n(1 ≤ n ≤ 10000)

  • 輸出
    輸出一個整數,表示不同的組合方案數

解析

看到這道題目,我的第一反映這是一道dp問題,因為類似的題目太多了,比如青蛙跳臺階,和這個題目是一樣的問法。一次可以跳幾階,和一次用幾種硬幣,然後都有一個目標值,總共為N元(N階)。所以最先想到的方法是遞迴求解,

f(N) = f(N-1)+f(N-5)+…+f(N-100)

然後按照一般的dp問題解決方法,寫出遞迴公式後,找遞迴出口。這個時候我就遇到了問題。1,5,10,20,50,100這幾個數字,需要多次判斷N的值與各個幣值的大小關係,而且與跳臺階本質上有一個不同的就是,跳臺階是一個順序的過程,而拼湊錢幣是沒有先後順序一說的,也就是,我只考察各個幣種的個數,而不關心拿出的順序。所以直接使用遞迴,會有大量的重複資料。

這個時候更換思路,不要從後往前遍歷所有的解,而是,從前往後推出所有的合法解。最粗暴的思路就是列舉,6層迴圈判斷所有合理解。然後考慮在這6層迴圈中,有沒有什麼限制條件:

  • 幣種1,對於任意N,都只有一種方法 1 * N

  • 幣種100(最大面值),其方法數,包含了許多子問題:100,可以使用的方法有 N/100種,那麼剩下的問題就是,N -k*100 為最終目標時,使用{1,5,10,20,50}這些幣種所構成的方法數。

根據上面的限制二,我們有看到熟悉的DP味道。這樣,不斷的把問題簡化為子問題,最終不就回到了只剩下幣種為1時的問題規模。現在我們就要考慮怎麼用程式碼來模擬這個過程的問題。類似DP問題,我們需要一個遞迴筆記本,記錄過程中所有的方法解,然後根據現有解,去推出未知解。

接下來看程式碼:

import java.util.*;

public class Main{
    public static long solution(int n) {
        int coins[] = { 1, 5, 10, 20, 50, 100 };
        int h = coins.length;
        long dp[][] = new long[h][n + 1];//存放所有解的筆記本,二維陣列存放
        //dp[x][y]:x 為當前可用幣種數目,y 為所需要湊的目標值即子問題的目標值
        Arrays.fill(dp[0], 1
);//當幣種為1時,對於任意N都為1種解法 for (int i = 1; i < h; i++) {//逐次增加幣種 for (int j = 1; j <= n; j++) {//逐次增加目標值 int m = j / coins[i];//當前問題,可用的最大幣種的數量 for (int k = 0; k <= m; k++) { //用K個最大幣種時,問題縮小為:用1~次最大幣種,目標值為j-K*coins[i]的解法有 dp[i][j] += dp[i - 1][j - k * coins[i]]; } } } //所有子問題解決,得到最終的解 return dp[h - 1][n]; } public static void main(String args[]) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); System.out.println(solution(n)) } }

總結

(個人看法,僅供參考)
DP問題的本質,是將問題分為若干個有限規模的子問題,然後根據子問題,得到最終解,並且子問題是相同的。不管是遞推還是遞迴,都是DP問題的解決思路,只不過一個是從初始狀態開始,類似迴圈列舉,遞迴是從結果開始。遞迴很難控制,整個程式的發展順序,一般都會遍歷到所有解,包括重複解,而遞推不同在於,可以控制,整個遞推的過程,從而篩選掉不需要的解。各有利弊,面對不同的問題,靈活運用最好。