暴力递归转动态规划(十四)

题目
arr是面值数组,其中的值都是正数且没有重复。再给定一个正数aim。
每个值都认为是一种面值,且认为张数是无限的。
返回组成aim的最少货币数

暴力递归
依然是面值张数的问题,暴力递归尝试的过程是从数组arr index = 0位置出发,一直到 index = arr.length。如果能够正好凑齐aim,则return 0,否则 return Integer.Max_Value用来进行区分。
需要注意的是:因为是求最少货币数,所以需要通过数组中下一个面值的张数使用 + 当前面值的张数使用,取最小值

代码
如果index == arr.length时,没有面值可以取了,如果此时rest = 0,说明正好凑齐钱数,return 0,否则return MAX_VALUE代表搞不了。
next != MAX_VALUE,则说明使用下一张面值刚好可以凑整aim钱数,那么就用当前面值的张数 zhang + 下一张面值的张数 next,和ans取最小值。
所以要通过MAX_VALUE来进行区分。

public static int minCoins(int[] arr, int aim) {
        return process(0, arr, aim);
    }

    public static int process(int index, int[] arr, int rest) {
        if (index == arr.length) {
            return rest == 0 ? 0 : Integer.MAX_VALUE;
        } else {
            int ans = Integer.MAX_VALUE;
            for (int zhang = 0; zhang * arr[index] <= rest; zhang++) {
                //要去index + 1位置看下一张面值的钱的使用情况
                int next = process(index + 1, arr, rest - zhang * arr[index]);
                // 如果使用下一张面值 恰好可以将 rest凑齐
                if (next != Integer.MAX_VALUE) {
                    //则,将当前使用面值张数 + 下一张面值使用张数 与 ans取最小值
                    ans = Math.min(ans, next + zhang);
                }
            }
            return ans;
        
    }

动态规划
根据暴力递归的代码改写动态规划,可变参数为 剩余金额 rest 和 数组下标 index,因为 rest 可以到达0位置,index可以到达arr.length。所以可以确定dp[][]的大小是 的 dp[arr.length + 1 ][aim + 1]。
确定好dp表大小后,根据暴力递归中的base case来填充dp表的默认值。
可以看到。暴力递归代码中,当index == arr.length时, 如果此时剩余金额 rest = 0,则return 0 ,否则 return MAX_VALUE。又因为int型数组,创建后,每个格子默认值就是0,所以只需要将dp[arr.length][1 ~ aim]的位置填充即可。其余代码照抄,dp表就填充完成了。

代码
暴力递归代码中,每次都是依赖index + 1位置 rest - zhang * arr[index],所以外层循环从N -1向0处遍历, 内循环 rest从0 向aim处遍历。

  public static int dp(int[] arr, int aim) {
        int N = arr.length;
        int[][] dp = new int[N + 1][aim + 1];

        for (int rest = 1; rest <= aim;rest ++){
            dp[N][rest] = Integer.MAX_VALUE;
        }
        for (int index = N - 1; index >= 0; index--) {
            for (int rest = 0; rest <= aim; rest++) {
                int ans = Integer.MAX_VALUE;
                for (int zhang = 0; zhang * arr[index] <= rest; zhang++) {
                    int next = dp[index + 1][rest - zhang * arr[index]];
                    if (next != Integer.MAX_VALUE) {
                        ans = Math.min(ans, next + zhang);
                    }
                    dp[index][rest] = ans;
                }
            }
        }
        return dp[0][aim];
    }

再次优化
可以看到动态规划的代码中,每填充1个dp表中格子都有枚举行为(zhang的for循环)。所以还可以再次进行优化。找到dp表中的依赖关系,替换掉这个for循环即可。
找依赖关系最直观的方法就是画图。
假设当前我剩余钱数 = 11,面值为3。
在这里插入图片描述
根据暴力递归代码带入看一下,我依赖的a0 ~ d3,当前面值使用0张,1张。。。的各种情况向下递归。
那如果此时rest = 8,依赖的就是b1 ~ d3,不过此时使用的张数也是0张、1张。。。。以此类推。
所以想要求 √ 位置的值,就是 a0 + (x + 1)。
为什么要加1呢? 还是回到暴力递归中的代码来看 这行代码 ans = Math.min(ans, next + zhang);
如果我next有效,我用的是 next + zhang,是在我当前面值使用了1,2,3若干张之后的情况,所以x位置,也是 √ 使用的一张arr[index]后剩余的rest = 8。

代码
首先保证左侧有值,并且左侧的值是有效的才进行比较。

public static int bestDP(int[] arr, int aim) {
        int N = arr.length;
        int[][] dp = new int[N + 1][aim + 1];

        for (int rest = 1; rest <= aim;rest ++){
            dp[N][rest] = Integer.MAX_VALUE;
        }
        for (int index = N - 1; index >= 0; index--) {
            for (int rest = 0; rest <= aim; rest++) {
                dp[index][rest] = dp[index + 1][rest];
                if (rest - arr[index] >= 0 && dp[index][rest - arr[index]] != Integer.MAX_VALUE) {
                    dp[index][rest] = Math.min(dp[index][rest], dp[index][rest - arr[index]] + 1);
                }
            }
        }
        return dp[0][aim];
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值