• 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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 时间: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];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

      说明: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),相当于对特例进行特殊优化。
  • 相关阅读:
    SpringBoot使用redis解决分页查询大量数据慢的情况
    给Python漫画分集标题下载工具添加线程
    Web前端:如何提高React原生应用性能
    Ubuntu(Linux)的基本操作
    消费大变革时代,三只松鼠的“高端性价比”如何突围制胜?
    目标检测算法yolo的python实现
    【博学谷学习记录】超强总结,用心分享|架构师-Kafka优化手段
    695. 岛屿的最大面积
    目标检测常见问题
    cmd命令行设置 windows 设置环境变量
  • 原文地址:https://blog.csdn.net/zheliku/article/details/133806589