Leetcode日记:322.换硬币

通过回溯和动态规划解决LeetCode第322题,求给定面额硬币凑成指定金额的最小硬币数。动态规划的关键在于构建二维数组,从0面值和0兑换金额开始,通过比较不同状态来填充表格。如果无法达到目标金额,返回-1。
摘要由CSDN通过智能技术生成

题目

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

【举例】
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1

分析

我们仍采取上次说的回溯转动态规划,来慢慢熟悉动态规划解题思路。

回溯

    public int minCoins(int[] coins, int amount){
        if(coins == null || coins.length == 0 || amount < 1){
            return -1;
        }
        return proress(coins, 0, amount);
    }

    /**
     * 
     * @param coins coin数组
     * @param i 当前考虑的第i个面值coin
     * @param rest 目前剩下需要换的钱
     * @return 
     */
    public int proress(int[] coins, int i, int rest) {
        // base case:
        // 此时已经没有面值能考虑的
        // 所以如果剩下的rest此时不是0,则返回-1
        if(i == coins.length){
            return rest == 0 ? 0 : -1;
        }
        // 最少张数,初始为-1
        int res = -1;
        // 依次尝试使用当前面值(coins[i])0张、1张,但不能超过rest
        for(int k = 0; k * coins[i] <= rest; k++){
            // 使用了k张coins[i],剩下的钱为rest-k*coins[i]
            int next = proress(coins, i +1, rest - k* coins[i]);
            if(next != -1){
                if(res == -1)
                    res = next + k;
                else
                    res = Math.min(res, next + k);
            }
        }
        return res;
    }

动态规划

  1. 找可变参数,如果看了回溯的代码,马上能确定分别是:硬币面值coins[i]、剩下的钱rest

  2. 构建表,二维数组的行代表硬币面值,列代表兑换面值,如下图所示。注意,为了构建base case,我们必须要创建0硬币面值,以及0兑换钱数,因为我把0面值放在了数组最后,所以我们运算方向是自下向上的
    开始建立二维数组

  3. 随后我们对base case进行初始赋值,当0面值时。只有当兑换钱总数为0,数量才是0,其他都是-1(不可达)

base case

  1. 最为关键的一步,如何填写非base case,我们要梳理清楚逻辑关系,首先赋值-1,然后我们考虑此时的coin[i]能否构成,不能构成(coin[i] > rest)我们肯定不可达,如果coins[i] < rest ,这是就要看 rest - coins[i] 是否可达,如果不可达,那rest也不可达,如果可达,我们就要和之前没有coins[i]参与的面值组成的rest比一比,谁更小。

以上这段文字需要细细品味,如果你觉得想起来有点吃力,请看下面两张图:
注意黄字
比较大小

这两张图中,我希望你搞清楚红色数字的来由,

    public int minCoins(int[] coins, int amount){
        if(coins == null || coins.length == 0 || amount < 1){
            return -1;
        }
        int N = coins.length;
        int[][] dp = new int[N + 1][amount + 1];
        // base case初始化
        for(int col = 1; col <= amount; col++){
            dp[N][col] = -1;
        }
        for(int i = N - 1; i >= 0; i--){
            for(int rest = 0; rest <= amount; rest++){
                dp[i][rest] = -1;
                if(dp[i + 1][rest] != -1)
                    dp[i][rest] = dp[i + 1][rest];
                if(rest - coins[i] >= 0 && dp[i][rest - coins[i]] != -1){
                    if(dp[i][rest] == -1)
                        dp[i][rest] = dp[i][rest - coins[i]] + 1;
                    else
                        dp[i][rest] = Math.min(dp[i][rest - coins[i]] + 1, dp[i][rest]);
                }
            }
        }
        return dp[0][amount];
    }

欢迎大家关注我的微信,现在周更算法以及java知识!
微信关注安知窝

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值