动态规划之找零钱问题(求最小硬币数)

本文解析了如何使用动态规划解决零钱兑换问题,介绍了动态规划的基本思想和在该问题中的应用。通过实例说明了如何构造状态转移方程,并提供了代码实现,包括一维和二维数组两种解法。重点在于理解组合规律和状态转移的过程。
摘要由CSDN通过智能技术生成

题目:零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 0 。你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1

首先我先阐述一下我对于动态规划的一些理解:
1.动态规划是通过运用结果集中产生的最优结果去推出下一需要的结果,即用0 ~ (n - 1)结果间结果的规律推出第n个结果。
2.动态规划通过计算最优解的值,通常自底向上或者自顶向下
3.动态规划就是将大问题分成小问题求解,将一个个小问题的解组合求得大问题的解。
我的理解暂时就是这么多,毕竟也是萌新。

题目分析:刚看问题感觉通过贪心算法也能求出来,但是如果coins = [5,3], amount = 9 的话会发现求不出来。这题用动态分析求的话我们要明白要求的结果集是什么,此题目的结果集是求0 ~amount 下可用的当前可用最少硬币(coins ),得result[i][j],i代表当前0 ~ i的可用硬币,j代表当前所求金额。我们通过表格寻找result[i][j]结果与result[0 ~ i][0 ~ j]间的关系,如下图:
在这里插入图片描述
在这里插入图片描述

分析:
1)对于coins硬币数组中有的是用与不用的区别
2)对于j >= 3的结果集中,我们发现amount (3)= amount (1)+ amount (2);amount (7)= amount (6)+ amount (1),amount (7)= amount (5)+ amount (2),amount (7)=amount (3)+ amount (4)…等这样的规律。为什么不说amount (9)= amount (3)*3呢,因为结果是和amount (6)+ amount (3)是一样的所以就不考虑了。

所以我们得出 result[i][j]结果与result[0 ~ i][0 ~ j]间的关系:

当 amount == coins[i]时,就取1;

当 amount != coins[i]时,就取组合结果中最小的数(参考分析结论2中的组合结果,注意考虑0,关于怎么求最小结果我就直接用代码展示了);

测试如下:
在这里插入图片描述
在这里插入图片描述
代码如下:

package 算法.动态规划;

import java.util.Scanner;

/*
*  零钱兑换
*
* */
public class AskingChange {
    int amount;
    int[] coins;

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int amount = scanner.nextInt();
        AskingChange askingChange = new AskingChange(amount);
        int coinsNumbers = scanner.nextInt();
        // 下标从1开始
        int[] coins = new int[coinsNumbers + 1];
        for (int i = 1; i < coins.length; i++) {
        		coins[i] = scanner.nextInt();
        	}
        askingChange.setCoins(coins);
        // 结果记录
        int[][] result = new int[coins.length][amount+1];

        askingChange.getMinCoins(coins.length-1, amount, result);
        System.out.println(result[coins.length-1][amount]);
        scanner.close();
    }

    public void getMinCoins(int coinsIndex,int amountLength,int[][] result){
        // 外层的for表示开放的coins可用数
        for (int i = 1; i <= coinsIndex; i++) {
        		for (int j = 1; j <= amountLength; j++) {
                    for (int k = 1; k <= i; k++) {
                        // 刚好等于面额
                        if (j == coins[k]) {
                            result[i][j] = 1;
                            break;
                        }
                        if (j != coins[k]) {
                            result[i][j] = getMin(result[i],j);
                        }
                    	}
        			}
        	}
    }

    // 获取0到i中组合方法的最小硬币数
    public int getMin(int[] result,int resultLength) {
        int min ;
        if (resultLength <= 1){
            return 0;
        }else if (resultLength ==2) {
            return result[1] * 2;
        }
        min = result[1]+result[resultLength-1];
        // 如果没结果
        if ((result[1] == 0) | (result[resultLength-1] == 0)){
            // 赋予int最大值的
            min = 2147483647;
        }
        for (int i = 1; i <= resultLength / 2; i++) {
                    // 跳过无结果的数
                    if ((result[i] == 0) | (result[resultLength - i] == 0)) {
                        continue;
                    }
            		if (min > (result[i] + result[resultLength - i])) {
            		    min = result[i] + result[resultLength - i];
                    }
            	}
        if (min == 2147483647){
            min = 0;
        }
        return min;
    }

    public AskingChange(int amount) {
        this.amount = amount;
    }

    public int getAmount() {
        return amount;
    }

    public void setAmount(int amount) {
        this.amount = amount;
    }

    public int[] getCoins() {
        return coins;
    }

    public void setCoins(int[] coins) {
        this.coins = coins;
    }
}

后来我发现其实用一维数组记录结果集也可以,就是不限制硬币必须从0~ i依次解开封印,而是可以直接用coins内所有的硬币,但是懒癌发作就懒得改了。写下博客记录一下学习过程,如果其中有什么说得不对的,望各位大佬指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值