[动态规划] leetcode 518. 零钱兑换 II

本文介绍了一种利用动态规划解决零钱兑换问题的方法,通过递推公式计算出凑成特定金额的不同硬币组合数量,并进行了时间与空间复杂度优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题描述:

  零钱兑换 II:给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。假设每一种面额的硬币有无限个。
  例子:输入amount = 5,coins = {1, 2, 5}; 输出4

动态规划求解

  令 d p [ i ] [ j ] dp[ i ][ j ] dp[i][j]使用前i种硬币兑换金额 j j j 的方法次数,数组coins[]表示所存取的硬币金额。当采用动态规划思想来解决本问题时,对于前 i i i种硬币兑换金额 j j j , 其可能是仅由前 i − 1 i-1 i1种硬币达到金额 j j j,不选取当前硬币;也可能是 i − 1 i-1 i1种硬币达到金额 j − c o i n s [ i − 1 ] j-coins[i-1] jcoins[i1],然后选取1枚第 i i i种硬币;也可能是 i − 1 i-1 i1种硬币达到金额 j − c o i n s [ i − 1 ] ∗ 2 j-coins[i-1]*2 jcoins[i1]2,然后选取2枚第 i i i种硬币.等等,直至选取第i种硬币的硬币数k不满足 j − c o i n s [ i − 1 ] ∗ k > = 0 j-coins[i-1]*k>=0 jcoins[i1]k>=0。所以 d p [ i ] [ j ] dp[ i ][ j ] dp[i][j]的计算方法可以表示为:
d p [ i ] [ j ] = Σ k = 0 j < = c o i n s [ i − 1 ] d p [ i − 1 ] [ j − c o i n s [ i − 1 ] ∗ k ] dp[ i ][ j ] = \Sigma^{j<=coins[i-1]}_{k=0}dp[i-1][j-coins[i-1]*k] dp[i][j]=Σk=0j<=coins[i1]dp[i1][jcoins[i1]k]
  如图所示,对于 d p [ 2 ] [ 4 ] dp[ 2 ][ 4] dp[2][4],此时可以选取的硬币值为2,则 d p [ 2 ] [ 4 ] = d p [ 1 ] [ 4 ] + d p [ 1 ] [ 2 ] + d p [ 1 ] [ 0 ] dp[ 2 ][ 4] = dp[1][4] + dp[1][2] + dp[1][0] dp[2][4]=dp[1][4]+dp[1][2]+dp[1][0]。特别的当 i = 0 , j = 0 i=0,j=0 i=0,j=0的时候,只有不拿取硬币一种情况 d p [ 0 ] [ 0 ] = = 1 dp[ 0][ 0 ] == 1 dp[0][0]==1。当 i = 0 , j ! = 0 i=0,j!=0 i=0j!=0时,不可能不拿硬币而达到金额j,故 d p [ 0 ] [ j ] = = 0 dp[ 0][ j] == 0 dp[0][j]==0。之后将 i i i递增,逐步求取每个 d p [ i ] [ j ] dp[ i ][ j ] dp[i][j]的值,当求取完整个数组时, d p [ c o i n s . n u m ] [ a m o u n t ] dp[ coins.num ][amount ] dp[coins.num][amount]即为硬币组合总数。

在这里插入图片描述

  程序实现需要编写三层循环,分别对应硬币种类金额i金额,金额金额j金额,可以选取的硬币数k。需要注意的是硬币种类 i i i循环时从1开始,金额 j j j循环时从0开始,获取第 i i i种硬币的价值时的位 c o i n s [ i − 1 ] coins[i-1] coins[i1]。运行结果为96 ms,47.8 MB。

class Solution {
    public int change(int amount, int[] coins) {
        int n = coins.length;
        int[][] dp = new int[n + 1][amount + 1];
        dp[0][0] = 1;
        for (int i = 1; i <= n ; i++) {
            for (int j = 0; j <= amount; j++) {
                for (int k = 0; k <= j/coins[i-1]; k++) {
                    dp[i][j] += dp[i-1][j - k * coins[i-1]];
                }
            }
        }
        return dp[n][amount];
    }
}

时间复杂度优化

  考虑到:
d p [ i ] [ j ] = Σ k = 0 j < = c o i n s [ i − 1 ] d p [ i − 1 ] [ j − c o i n s [ i − 1 ] ∗ k ] dp[ i ][ j ] = \Sigma^{j<=coins[i-1]}_{k=0}dp[i-1][j-coins[i-1]*k] dp[i][j]=Σk=0j<=coins[i1]dp[i1][jcoins[i1]k]
d p [ i ] [ j − c o i n s [ i − 1 ] ] = Σ k = 0 j − c o i n s [ i − 1 ] < = c o i n s [ i − 1 ] d p [ i − 1 ] [ j − c o i n s [ i − 1 ] − c o i n s [ i − 1 ] ∗ k ] dp[ i ] [j-coins[i-1] ] = \Sigma^{j-coins[i-1]<=coins[i-1]}_{k=0}dp[i-1][j-coins[i-1]-coins[i-1]*k] dp[i][jcoins[i1]]=Σk=0jcoins[i1]<=coins[i1]dp[i1][jcoins[i1]coins[i1]k]
  则有

d p [ i ] [ j ] = d p [ i ] [ j − c o i n s [ i − 1 ] ] + d p [ i − 1 ] [ j ] dp[ i ][ j ] = dp[ i ][ j-coins[i-1]] + dp[i-1][j] dp[i][j]=dp[i][jcoins[i1]]+dp[i1][j]

  所以按照之前的算法会有很多重复计算的过程, d p [ i ] [ j ] dp[ i ][ j ] dp[i][j]可以由 d p [ i ] [ j − c o i n s [ i − 1 ] ] dp[ i ][ j-coins[i-1]] dp[i][jcoins[i1]] d p [ i − 1 ] [ j ] dp[ i -1][ j ] dp[i1][j]直接推出,这样可以很好的避免重复的计算(对应减少硬币数 k k k的循环),从而减小时间复杂度。对应这里有 d p [ 2 ] [ 4 ] = d p [ 1 ] [ 4 ] + d p [ 1 ] [ 2 ] + d p [ 1 ] [ 0 ] dp[ 2 ][ 4] = dp[1][4] + dp[1][2] + dp[1][0] dp[2][4]=dp[1][4]+dp[1][2]+dp[1][0]

在这里插入图片描述

  程序实现需要编写两层循环,分别对应硬币种类 i i i,金额 j j j。运行结果为5 ms 47.9 MB。可以看到运行时间大大缩短。

class Solution {
    public int change(int amount, int[] coins) {
        int n = coins.length;
        int[][] dp = new int[n + 1][amount + 1];
        dp[0][0] = 1;
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= amount; j++) {
                if(j >= coins[i-1]){
                    dp[i][j] = dp[i-1][j] + dp[i][j - coins[i-1]];
                }else{
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        return dp[n][amount];
    }
}

空间复杂度优化

考虑到 d p [ i ] [ j ] dp[ i ][ j ] dp[i][j]的值仅与第 i − 1 i-1 i1行有关系,可以采用滚动数组的思想来降低空闲复杂度。具体的,只维护一维的 d p dp dp数组,其状态更新关系为:
d p [ j ] = d p [ j − c o i n s [ i − 1 ] ] + d p [ j ] dp[ j ] = dp[ j-coins[i-1]] + dp[j] dp[j]=dp[jcoins[i1]]+dp[j]

在这里插入图片描述

  程序实现需要编写两层循环,分别对应硬币数i,金额j。运行结果为4 ms 38.8 MB,可以看到空间使用情况有所降低。

class Solution {
    public int change(int amount, int[] coins) {
        int n = coins.length;
        int[] dp = new int[amount + 1];
        dp[0]= 1;
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= amount; j++) {
                if(j >= coins[i-1]){
                    dp[j] = dp[j] + dp[j - coins[i-1]];
                }else{
                    dp[j] = dp[j];
                }
            }
        }
        return dp[amount];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值