LeetCode 面试题 08.11. 硬币

一、题目

  硬币。给定数量不限的硬币,币值为25分、10分、5分和1分,编写代码计算n分有几种表示法。(结果可能会很大,你需要将结果模上1000000007)

示例1:

输入: n = 5
输出: 2
解释: 有两种方式可以凑成总金额:
5=5
5=1+1+1+1+1

示例2:

输入: n = 10
输出: 4
解释: 有四种方式可以凑成总金额:
10=10
10=5+5
10=5+1+1+1+1+1
10=1+1+1+1+1+1+1+1+1+1

说明:

  • 0 <= n (总金额) <= 1000000

  点击此处跳转题目

二、C# 题解

2.1 数学解法

  使用数学方法解答,将 n 看作是 25、10、5 和 1 的组合,即 n = 25a + 10b + 5c + d。因为给定 n 后,d 由 a、b、c 决定,因此仅讨论 a、b、c 之间的关系即可。

  将 a、b、c 降序排列,其关系如下所示:

  • 给定 a 不动,讨论 b 和 c 的组合数。

    • b 不动,c 依次递减(每次 c 减 1,少的面值加到了 d 上,即 d += 5),共 c + 1 种。
    • b 减 1,则 c += 2,共 c + 3 种。
    • b 减 2,则 c += 4,共 c + 5 种。
    • 直至 b 减为 0,此时 c + b * 2,共 c + b * 2 + 1种。

    共计 (b + 1)(b + c +1) 种。

  • a 减 1,则多出 25 分,总面值为 25 + 10b + 5c。由 b、c 重新降序排列,即10b’ + 5c’ = 25 + 10b + 5c,解得 b’ = b + (5 + c) / 2,c’ = (c + 5) % 2。

public class Solution {
    public int WaysToChange(int n) {
        int a = n / 25, b = n % 25 / 10, c = n % 25 % 10 / 5;
        return (int)Partition(a, b, c);
    }

    public long Partition(int a, int b, int c) {
        if (a < 0) return 0;
        long n = (b + 1L) * (b + c + 1L) % 1000000007; // 使用 1L 进行 long 运算防止结果溢出
        return (Partition(a - 1, b + (c + 5) / 2, (c + 5) % 2) + n) % 1000000007;
    }
}
  • 时间:24 ms,击败 100.00% 使用 C# 的用户
  • 内存:27.60 MB,击败 57.14% 使用 C# 的用户

2.2 动态规划

  使用动态规划解答,记 f(i,j) 表示用前 i 种硬币构成面值 j 的种类数,因此有

f ( i , j ) = f ( i − 1 , j ) + f ( i − 1 , j − c i ) + f ( i − 1 , j − 2 ⋅ c i ) + ⋯ + f ( i − 1 , j − k ⋅ c i ) f(i,j)=f(i-1,j)+f(i-1,j-c_i)+f(i-1,j-2\cdot c_i)+\cdots+f(i-1,j-k\cdot c_i) f(i,j)=f(i1,j)+f(i1,jci)+f(i1,j2ci)++f(i1,jkci)

  其中,c_i 表示第 i 种硬币的面值,k = j / c_i

进行优化:

  • 时间复杂度:通过上式可以看出, f ( i , j ) = f ( i − 1 , j ) + f ( i , j − c i ) f(i,j)=f(i-1,j)+f(i,j-c_i) f(i,j)=f(i1,j)+f(i,jci),避免了多一层的遍历
  • 空间复杂度:由于上式为递推式,仅和 ii - 1 有关,类似斐波那契数列的求解,只需要一个一维数组存储数据即可,数组内容不断更新 1 、 2 、 ⋯ 、 i 1、2、\cdots、i 12i 对应的值。

  事实上,动态规划的解法思想和数学解法很类似,即 c 1 , c 2 , c 3 , c 4 c_1,c_2,c_3,c_4 c1,c2,c3,c4 对应于数学解法的 d , c , b , a d,c,b,a d,c,b,a

public class Solution {
    public int WaysToChange(int n) {
        int[] coins = { 1, 5, 10, 25 };
        int[] f     = new int[n + 1];
        f[0] = 1;
        for (int i = 0; i < 4; i++)
        for (int j = coins[i]; j < n + 1; j++) {
            f[j] += f[j - coins[i]];
            f[j] %= 1000000007;
        }
        return f[n];
    }
}

  说明:f[j] += f[j - coins[i]] 为化简写法,即 f[j] = f[j] + f[j - coins[i]],对应于 f ( i , j ) = f ( i − 1 , j ) + f ( i , j − c i ) f(i,j)=f(i-1,j)+f(i,j-c_i) f(i,j)=f(i1,j)+f(i,jci)

  • 时间:44 ms,击败 42.86% 使用 C# 的用户
  • 内存:29.33 MB,击败 28.57% 使用 C# 的用户

  动态规划的方法没有数学解法快,这里的解释是:

  • 动态规划为通解。即,coins 可以换成任意其他数组,更具有普遍性。
  • 数学方法为特例。即,利用了 1、5、10、25 之间的数学关系(b 减 1,c += 2),相当于对特例进行特殊优化。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蔗理苦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值