02-动态规划-凑零钱问题

问题描述:

 先给你k种面值的硬币,面值分别为c1, c2, ..., ck,每种硬币的数量无限,再给一个总金额amount,问你最少需要几枚硬币凑出这个金额,如果不能凑出,算法返回-1.

1.暴力解法

问题分析:我们相求amount = 11时的最少硬币数,如果我知道amount=10(子问题)的最少硬币数,只需要把子问题的答案加1(在选择一枚面值为1的硬币)就是原问题的答案。

如何确定正确的状态转移方程:

(1)确定base case:  目标金额amount = 0的时候,返回0.

(2)确定“状态”,也就是原问题和子问题的变量:本题种硬币数量无限,金额给定,只有目标金额amount的状态会不断的向base case靠近,所以唯一的变量就是amount.

(3) 确定“选择”,也就是导致“状态”产生变化的行为:目标金额amount怎样发生变化,是因为你选择硬币,每选择一枚硬币,目标金额就会减少硬币对应的面额。所以说所有硬币的面值就是你的选择。

(4)明确dp函数/数组的定义:一般来说函数的参数就是状态转移的变量;函数的返回值就是题目中要求我们计算的量。本题中的状态只有一个,即“目标金额”,题目要求凑出目标金额所用的最少硬币数量就是我们计算的结果。

所以dp(n)  定义:输入一个目标金额n,返回凑出目标金额n的最少硬币数量。

public class ChooseCoin{

  private int amount;

  private List<Integer> coins;

   
  int dp(int n) {
    if (n == 0) {
      return 0;
    }
    if (n < 0) {
      return -1;
    }
    //正无穷
    int res = Integer.MAX_VALUE;
    for (Integer coin: coins) {
     int subProblem = dp(n-coin);
     if (subProblem == -1) {
       continue;
     }
     res = Math.min(res, subProblem+1);
    }
    
    return res != Integer.MAX_VALUE ? res : -1;
  }
}

2.带备忘录的递归解法

自顶向下

 static int dp(Integer n, List<Integer> coins, Map<Integer, Integer> memo) {
     //提前返回,减少重复计算   
     if (MapUtils.isNotEmpty(memo) && Objects.nonNull(memo.get(n))) {
            return memo.get(n);
        }
        if (n == 0) {
            return 0;
        }
        if (n < 0) {
            return  -1;
        }
        int res = Integer.MAX_VALUE;
        for (Integer coin : coins) {
           int temp = dp(n-coin, coins, memo);
           if (temp == -1) {
               continue;
           }
           res = Math.min(res, 1 + temp);
           memo.put(n, res != Integer.MAX_VALUE ? res : -1);
        }
        return memo.get(n);
    }

3. dp数组的迭代解法

dp数组的定义和前面的dp函数的定义类似,也是把“状态”,也就是目标金额作为变量。不过dp函数的“状态”体现在参数,而dp数组的“状态”体现在数组的索引。

dp数组定义:当目标金额为i时,至少需要dp[i]的硬币凑出。

static int coinChange(List<Integer> coins, int amount) {
        if (amount < 0) {
            return -1;
        }
        if (amount == 0) {
            return 0;
        }
        //填充数组每个值为amount+1,代替无穷大
        int[] dp = new int[amount +1];
        Arrays.fill(dp, amount+1);
        //base case
        dp[0] = 0;
        //外层for循环,遍历所有状态的所有取值
        for (int i = 0; i< dp.length; i++ ) {
            //内层for循环求所有选择的最小值
            for (Integer coin : coins) {
                //子问题无解,跳过
                if (i - coin < 0) {
                    continue;
                }
                dp[i] = Math.min(dp[i], dp[i-coin] +1);
            }
        }
        return dp[amount] == amount+1 ? -1 : dp[amount];
    }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值