面试题 08.11. 硬币

  • 难度中等
  • 本题涉及算法动态规划(dp)
  • 思路完全背包问题

题目 面试题 08.11. 硬币

硬币。给定数量不限的硬币,币值为25分、10分、5分和1分,编写代码计算n分有几种表示法。(结果可能会很大,你需要将结果模上1000000007)

示例

示例1:

输入: n = 5
输出:2
解释: 有两种方式可以凑成总金额:
5=5
5=1+1+1+1+1

示例2:

输入: n = 10
输出:4
解释: 有四种方式可以凑成总金额:
10=10
10=5+5
10=5+1+1+1+1+1
10=1+1+1+1+1+1+1+1+1+1
说明:

注意:

你可以假设:

0 <= n (总金额) <= 1000000

解题思路

由于无限数量硬币的选择,应该是一个完全背包问题

  • dp 数组建立:dp[i][j] 表示 i 种硬币组成面值为 j 时的方法数

  • 考虑 base case

    • dp[0][j] 0 种硬币组成面值 j,不可能有方案,因此是 0, java 初始化时数组是 0,不用特殊处理

    • dp[i][0] 多种硬币组成面值 0,只有一种方案,就是一枚也不选

  • 状态转移方程:

    • dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i])

    • 其中 dp[i - 1][j] 表示当前硬币不选,那么由 i - 1 种组成面值 j

    • dp[i][j - coins[i]) 表示当前硬币选了,那么还需要组成面额为 j - coins[i], 这都是已要组成的面值大于当前硬币值为前提的。

因此可以先写出一个二维 dp 的代码,再进一步进行优化,状态压缩

代码 二维 dp

public int waysToChange(int n) {
        int[] coins = new int[]{1, 5, 10, 25};
        // 1. dp 表示对应的方法数 比如 dp[i][j] 表示 i 种硬币组成面值为 j 时的方法数
        // 2. 多开一个位置,0 空着不用 在下面对dp[0][j] dp[i][0] 做特殊处理
        int[][] dp = new int[5][n + 1];//
        // 对dp[0][j] dp[i][0] 做特殊处理  且dp[0][j] 不存在
        for (int i = 1; i <= 4; i++)
            dp[i][0] = 1;
        for (int i = 1; i <= 4; i++) { // i表示 i种币值,正好也对于coins
            for (int j = 1; j <= n; n++) {// j表示 组成的面值
                if (j - coins[i - 1] < 0) // 要组成的面值比当前硬币金额小,该硬币不可以选择
                    dp[i][j] = dp[i - 1][j] % 1000000007; // 只能由 i - 1 中硬币来组成面值 j
                else
                    dp[i][j] = (dp[i - 1][j] + dp[i][j - coins[i - 1]]) % 1000000007;
            }
        }

        return dp[4][n];
    }

代码 一维 dp

public int waysToChange(int n) {
        int[] coins = new int[]{1, 5, 10, 25};
        int[] dp = new int[n + 1];
        dp[0] = 1;
        for (int coin : coins) {
            for (int i = 1; i <= n; i++) {
                if (i - coin >= 0) {
                    dp[i] = (dp[i] + dp[i - coin]) % 1000000007; // sum = sum+ 1 第一个sum 和第一个sum 相差一个状态
                }
            }
        }
        return dp[n];
    }