当先锋百科网

首页 1 2 3 4 5 6 7

动态规划版找零钱问题

假设存在2,3,5元三种硬币,给定一定数量的钱,需要换成这三种硬币,并且使用最少的硬币数量
这个问题的本质是子问题最优解,由子问题最优解上构造出来的更高级的解也是最优解
假设你需要找出2的找钱方案,可以直接取得最优解1,找出3,可以直接找出最优解1,找出4,此时问题似乎有些麻烦,但是如果4是可以被找出的话,那么它一定由2,3,5组成,也就是说由2,3,5组成4的最后一步必然是2,3,5其中一个,那么我们就可以循环这三种硬币,4-2得2,2有最优解1,4-3得1,没有解,4-5小于0也没有,那么答案显而易见,就是2的最优解+1,也就是2就是最优解,那么任何数都可以通过这种方法来获得最优解,只要它是有解的
代码如下

public class App {
    @Test
    public void Test1(){
        //这里设置硬币种类
        int[] coins =new int[]{2,3,5};
        int min=change(coins,10);
        System.out.println(min);
    }
    public int change(int[] coins, int num) {
        //如果为0,无解
        if (num == 0) return 0;
        //默认创建一个能容纳5个零钱的数组,这里是为了和下面配合所以默认要5个大小,因为从1开始需要6的大小
        //这里代表每个数字的可能性,所以不从0开始而是1
        int[] optimal = new int[6];
        //这里同上
        if (num > 6) optimal = new int[num + 1];
        //给所有位置填上-1代表还没有解
        Arrays.fill(optimal, -1);
        //循环给硬币种类填上1,因为这几个数字只有唯一解
        //因为这里需要给2,3,5默认情况,所以数组直接大于5,这也就是为什么上面要设置6的大小
        for (int coin : coins) optimal[coin] = 1;
        //从1开始循环到21,也就是所有情况
        for (int v = 1; v <= num; v++) {
            //如果v个硬币已经有解的话,就跳出这次循环
            if (optimal[v] != -1) continue;
            //如果v个硬币暂时没给出解时,循环三种硬币种类
            for (int coin : coins) {
                //如果第v种减去2,3,5中任何一种小于0了或者结果暂时无解,就跳出这次循环
                //存在的情况例如,6-5,此时为1,但是1无解
                if (v - coin < 0 || optimal[v - coin] == -1) continue;
                //如果v个硬币情况未标注解或者v-2,3,5中任何一个有解并且比当前解更优
                //并且上面那个if已经规避了v-2,3,5会出现无解的情况,所以尽管把当前的解填为当前情况的最优解
                if (optimal[v] == -1 || optimal[v - coin] + 1 < optimal[v])
                    //把v个硬币的最优解填为v-2,3,5情况里的最优解+1步
                    optimal[v] = optimal[v - coin] + 1;
            }
        }
        //返回数组内v种硬币的最优解
        return optimal[num];
    }
}

代码来自于刘铁猛老师的算法之禅:递推与递归
这篇博客也是来自于我看过书之后的理解和思考