LeetCode --- 322. 零钱兑换

假设有25分、20分、5分、1分的硬币,现要找给客户41分的零钱,如何办到硬币个数最少?
***此前用贪心策略得到的并非是最优解(贪心得到的解释5枚硬币)

  • 假设dp(n)是凑到n分需要的最少硬币个数
  1. 如果第1次选择了25分的硬币,那么dp(n) = dp(n - 25) + 1
  2. 如果第1次选择了20分的硬币,那么dp(n) = dp(n - 20) + 1
  3. 如果第1次选择了5分的硬币,那么dp(n) = dp(n - 5) + 1
  4. 如果第1次选择了1分的硬币,那么dp(n) = dp(n - 1) + 1

所以 dp(n) = min { dp(n - 25), dp(n - 20), dp(n - 5), dp(n - 1) } + 1

找零钱 - 暴力递归

int coins(int n) {
    if (n < 1) return Integer.MAX_VALUE;
    if (n == 1 || n == 5 || n == 20 || n == 25) return 1;
    int min1 = Math.min(coins(n - 1), coins(n - 5));
    int min2 = Math.min(coins(n - 20), coins(n - 25));
    return Math.min(min1, min2) + 1;
}
  • 类似于斐波那契数列的递归版,会有大量的重复计算,时间复杂度较高

找零钱 - 记忆化搜索

int coins(int n) {
    if (n < 1) return -1;
    int[] dp = new int[n + 1];
    int[] faces = {1, 5, 20, 25};
    for (int face : faces) {
        if (n < face) break;
        dp[face] = 1;
    }
    return coins(n, dp);
}
static int coins(int n, int[] dp) {
    if (n < 1) return Integer.MAX_VALUE;
    if (dp[n] == 0) {
        int min1 = Math.min(coins(n - 25, dp), coins(n - 20, dp));
        int min2 = Math.min(coins(n - 5, dp), coins(n - 1, dp));
        dp[n] = Math.min(min1, min2) + 1;
    }
    return dp[n];
}

找零钱 - 递推

int coins(int n) {
    if (n < 1) return -1;
    int[] dp = new int[n + 1];
    for (int i = 1; i <= n; i++) {
        int min = dp[i - 1];
        if (i >= 5) min = Math.min(dp[i - 5], min);
        if (i >= 20) min = Math.min(dp[i - 20], min);
        if (i >= 25) min = Math.min(dp[i - 25], min);
        dp[i] = min + 1;
    }
    return dp[n];
}
  • 时间复杂度、空间复杂度:O(n)

思考题:请输出找零钱的具体方案(具体是用了哪些面值的硬币)

int coins(int n) {
    if (n < 1) return -1;
    int[] dp = new int[n + 1];
    int[] faces = new int[dp.length];
    for (int i = 1; i <= n; i++) {
        int min = dp[i - 1];
        faces[i] = 1;
        if (i >= 5 && dp[i - 5] < min) {
            min = dp[i - 5];
            faces[i] = 5;
        }
        if (i >= 20 && dp[i - 5] < min) {
            min = dp[i - 5];
            faces[i] = 20;
        }
        if (i >= 25 && dp[i - 25] < min) {
            min = dp[i - 25];
            faces[i] = 25;
        }
        dp[i] = min + 1;
    }
    print(faces, n);
    return dp[n];
}
void print(int[] faces, int i) {
    while (i > 0) {
        System.out.print(face[i] + "");
        i -= faces[i];
    }
    System.out.println();
}

找零钱 - 通用实现

int coins(int n, int[] faces) {
    if (n < 1 || faces == null || faces.length == 0) return -1;
    int[] dp = new int[n + 1];
    for (int i = 1; i <= n; i++) {
        int min = Integer.MAX_VALUE;
        for (int face : faces) {
            if (i < face) continue;
            if (dp[i - face] < 0 || dp[i - face] >= min) continue;
            min = dp[i - face];
        }
        if (min == Integer.MAX_VALUE) {
            dp[i] = -1;
        } else {
            dp[i] = min + 1;
        }
    }
    return dp[n];
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值