code35 纪念币问题

161 阅读1分钟

题目描述

  • 给定两个数组: n1 n2
  • n1[i]: 表示面值,n1币种为普通币,可以取任意枚
  • n2[i]: 表示面值,n1币种为纪念币,只能取一枚
  • 问能用多少种方法拼出aim的面值

code

public class Code35 {

    /**
     * @param commonCoin 普通硬币
     * @param goldCoin   纪念币
     * @param money      钱数
     */
    public static int money(int[] commonCoin, int[] goldCoin, int money) {
        if (money < 0) {
            return 0;
        }
        if ((commonCoin == null || commonCoin.length == 0) && (goldCoin == null || goldCoin.length == 0)) {
            return money == 0 ? 1 : 0;
        }
        // 任意张 的数组, 一张的数组,不可能都没有
        int[][] dparb = commonCoin(commonCoin, money);
        int[][] dpone = goldCoin(goldCoin, money);
        if (dparb == null) { // 任意张的数组没有,一张的数组有
            return dpone[dpone.length - 1][money];
        }
        if (dpone == null) { // 任意张的数组有,一张的数组没有
            return dparb[dparb.length - 1][money];
        }
        int res = 0;
        for (int i = 0; i <= money; i++) {
            res += dparb[dparb.length - 1][i] * dpone[dpone.length - 1][money - i];
        }
        return res;
    }

    /**
     * 只用纪念币(普通背包问题)
     * <p>
     * dp[i][j] 0...i号货币自由选择,搞定j钱数的方法数 (每个货币只可以使用一次)
     * 第一列:搞定0元,一张也不要 全是1
     * 第一行:j == arr[i]时为1 否则为0 (这里只能用一次)
     * 普通位置:
     * 不使用i号货币 dp[i - 1][j]
     * 使用i号货币 dp[i-1][j-arr[i]]
     */
    public static int[][] goldCoin(int[] arr, int money) {
        if (arr == null || arr.length == 0) {
            return null;
        }
        int[][] dp = new int[arr.length][money + 1];
        for (int i = 0; i < arr.length; i++) {
            dp[i][0] = 1;
        }
        if (arr[0] <= money) {
            dp[0][arr[0]] = 1;
        }
        for (int i = 1; i < arr.length; i++) {
            for (int j = 1; j <= money; j++) {
                dp[i][j] = dp[i - 1][j];
                dp[i][j] += j - arr[i] >= 0 ? dp[i - 1][j - arr[i]] : 0;
            }
        }
        return dp;
    }

    /**
     * 只用普通货币
     * dp[i][j] 0...i号货币自由选择,搞定j钱数的方法数(每个货币可以使用无限次)
     * <p>
     * 第一列:搞定0元,一张也不要 全是1
     * 第一行:j == arr[i]的倍数 时为1 否则为0
     * <p>
     * dp[i][j]  = dp[i-1][j] + dp[i][j-arr[i]]  (j-arr[i] 不越界)
     */
    public static int[][] commonCoin(int[] arr, int money) {
        if (arr == null || arr.length == 0) {
            return null;
        }
        int[][] dp = new int[arr.length][money + 1];
        // dp[i][j] 0..i券 自由选择张数, 搞定j元, 有多少方法?

        for (int i = 0; i < arr.length; i++) {
            dp[i][0] = 1;
        }
        // [0] 5元 0元 5元 10元 15元 20元
        for (int j = 1; arr[0] * j <= money; j++) {
            dp[0][arr[0] * j] = 1;
        }
        // 0行 0列 填完了
        for (int i = 1; i < arr.length; i++) {
            for (int j = 1; j <= money; j++) {
                dp[i][j] = dp[i - 1][j];
                dp[i][j] += j - arr[i] >= 0 ? dp[i][j - arr[i]] : 0;
            }
        }
        return dp;
    }


}