硬币找零,即数字和为sum的几种问题

问题一

1.问题描述
假设有几种硬币,如1、3、5,并且数量无限。请找出能够组成某个数目的找零所使用最少的硬币数。

2.问题分析
动态规划的思想,用 dp[] 存放自底向上问题的解。dp[] 大小为 amount,dp[k] = min(dp[k – coin[i]) + 1

3.Java实现

public int coinChange(int[] coins, int amount) {
    int max = amount + 1;             
    int[] dp = new int[amount + 1];  
    //先令 dp[] 初始化为 amount + 1
    Arrays.fill(dp, max);  
    //当 amount = 0 时,解也是 0
    dp[0] = 0;   
    for (int i = 1; i <= amount; i++) {
        for (int j = 0; j < coins.length; j++) {
            if (coins[j] <= i) {
                dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
            }
        }
    }
    //如果 dp[amount] > amount,即还是初始化的值,说明无解
    return dp[amount] > amount ? -1 : dp[amount];
}

问题二

1.问题描述
将上题中的求最小的硬币数,改成所有的方案。即,假设有几种硬币,并且数量无限。请找出能够组成某个数目的找零所有的方案数。

2.问题分析
用 dp[i, j] 表示:使用 第 1,2,…i 种面值的硬币时,需要找金额为 j 的钱,最多可采用多少种不同的方式。
i 表示可用的硬币种类数, j 表示 需要找回的零钱
有两种情况:对于某种面值的硬币,要么使用了它,dp[i, j – coins[i]]
要么不使用它,dp[i – 1, j]
所以,dp[i, j] = dp[i, j – coins[i]] + dp[i – 1, j]

3.Java实现

public int coinChangeWays(int[] coins, int n){
    int m = coins.length;
    int[][] dp = new int[m+1][n+1];

    //第一列 dp[i][0] 表示用coins[i]组成面额为0的钱的方案,即不需要任何货币,初始化为1
    for(int i = 0; i <= m; i++)
        dp[i][0] = 1;
    //第一行 dp[0][j] 没有货币,初始化为0
    for(int i = 1; i <= n; i++)
        dp[0][i] = 0;

    for(int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++){
            if (j - coins[i] >= 0)
                dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i]];
            else 
                dp[i][j] = dp[i - 1][j];
        }
    }
    return dp[m][n];
}

问题三

1.问题描述
将上题中硬币无限量使用,改为每个面值只有一个硬币。即,假设有几种硬币,每种硬币只有一个。请找出能够组成某个数目的找零所有的方案数。

题目还可以这样,同一个意思:
给定一个有n个正整数的数组A和一个整数sum,求选择数组A中部分数字和为sum的方案数。
当两种选取方案有一个数字的下标不一样,我们就认为是不同的组成方案。

2.问题分析
也是两种方法:要么加这个数,dp[i – 1, j – weight[i]]。和上题不同的只是,加上这个数,i 也要减一。
要不不加这个数,dp[i – 1, j]
所以,dp[i, j] = dp[i-1, j] + dp[i-1, j-weight[i]]

3.Java实现

public int sunWays(int[] weight, int sum) {
    int n = weight.length;
    int[][] dp = new int[n+1][sum+1];

    for (int i = 0 ; i < n ;i++) {
        dp[i][0] = 1;
    }
    for (int j = 1 ; j < sum ;j++) {
        dp[0][j] = 0;
    }

    for (int i = 1 ; i <= n ;i++) {
        for (int j = 0 ; j <= sum ;j++) {
            if(weight[i] <= j) 
                dp[i][j] = dp[i-1][j] + dp[i-1][j-weight[i]];
            else 
                dp[i][j] = dp[i-1][j];
        }
    }
    return dp[n][sum];
}

上面的方法,dp[][]用了二维数组, 也可以用一维数组来节省空间。

public int sumWays(int[] weight, int sum){
    int dp[]=new int[sum+1];
    dp[0]=1;

    for(int i = 0; i < weight.length; i++){
        for(int j = sum; j >= weight[i]; j--){
           dp[j] = dp[j - weight[i]] + dp[j];
        }
    }
    return dp[sum];
}
点赞