买钢笔和铅笔的方案数

标签: 数学 枚举

难度: Medium

给你一个整数 total ,表示你拥有的总钱数。同时给你两个整数 cost1 和 cost2 ,分别表示一支钢笔和一支铅笔的价格。你可以花费你部分或者全部的钱,去买任意数目的两种笔。

请你返回购买钢笔和铅笔的 不同方案数目 。

示例 1:

输入:total = 20, cost1 = 10, cost2 = 5
输出:9
解释:一支钢笔的价格为 10 ,一支铅笔的价格为 5 。
- 如果你买 0 支钢笔,那么你可以买 0 ,1 ,2 ,3 或者 4 支铅笔。
- 如果你买 1 支钢笔,那么你可以买 0 ,1 或者 2 支铅笔。
- 如果你买 2 支钢笔,那么你没法买任何铅笔。
所以买钢笔和铅笔的总方案数为 5 + 3 + 1 = 9 种。

示例 2:

输入:total = 5, cost1 = 10, cost2 = 10
输出:1
解释:钢笔和铅笔的价格都为 10 ,都比拥有的钱数多,所以你没法购买任何文具。所以只有 1 种方案:买 0 支钢笔和 0 支铅笔。

提示:

  • 1 <= total, cost1, cost2 <= 106

Submission

运行时间: 26 ms

内存: 16.1 MB

# 32ms code
def f(a, b, c, n):
    res = 0
    if a >= c:
        res += (a // c) * n * (n+1) // 2
        a %= c
    if a == 0:
        return res + (b // c) * (n + 1)
    
    if b >= c:
        res += (b // c) * (n + 1)
        b %= c
    m = (a * n + b) // c
    return res + m * n - f(c, c - b - 1, a, m-1)

class Solution:
    def waysToBuyPensPencils(self, total: int, cost1: int, cost2: int) -> int:
        a = cost1
        b = total % cost1
        c = cost2
        n = total // cost1
        return n + 1 + f(a, b, c, n)

Explain

此题解采用了一个递归函数,以及主函数计算购买方案的数目。主函数首先确定能购买的最大钢笔数量n,然后计算剩余的钱数b。递归函数f(a, b, c, n)用来计算钢笔和铅笔的购买组合数,其中a为钢笔价格,b为剩余钱数,c为铅笔价格,n为最大可以购买的钢笔数量。递归函数首先检查a是否大于等于c,若是,根据等差数列求和公式计算组合数。然后剩余的钱数b用来检查能否直接按价格c买铅笔。接着,如果b还有剩余,则继续计算购买m支铅笔后还能买多少支钢笔的情况,通过递归调用f计算。这个递归过程会逐渐减小问题的规模,直到一方商品买不起为止。

时间复杂度: O(total / cost2)

空间复杂度: O(total / cost2)

# 32ms code
def f(a, b, c, n):
    res = 0
    # 处理整除情况,增加快速计算部分方案数
    if a >= c:
        res += (a // c) * n * (n+1) // 2
        a %= c
    if a == 0:
        return res + (b // c) * (n + 1)
    # 处理余数部分
    if b >= c:
        res += (b // c) * (n + 1)
        b %= c
    m = (a * n + b) // c
    return res + m * n - f(c, c - b - 1, a, m-1)

class Solution:
    def waysToBuyPensPencils(self, total: int, cost1: int, cost2: int) -> int:
        a = cost1
        b = total % cost1
        c = cost2
        n = total // cost1
        # 计算总方案数,包括全不买的情况
        return n + 1 + f(a, b, c, n)

Explore

这个条件用于检查当前钢笔的价格`a`是否大于等于铅笔的价格`c`。当`a >= c`时,可以将钢笔的价格对铅笔的价格进行取模操作,从而简化计算。这是因为,如果钢笔价格高于或等于铅笔价格,那么在计算购买组合时,可以将部分钢笔价格转换为多个铅笔的购买,这样可以减少后续计算的复杂度。通过取模后,钢笔的新价格(即余数)会变小,从而在后续递归调用中减少计算量。

这行代码利用了等差数列的求和公式来快速计算在钢笔价格模铅笔价格后,仍然能够购买的铅笔数量。`a // c`计算了每种钢笔购买方案中可以额外购买的铅笔数量,而`n * (n+1) // 2`则是从0到n的等差数列和,代表了不同购买数量的钢笔时可以额外获得的铅笔总数。这种方式可以在不逐一枚举每种购买方案的情况下,快速计算出可以额外购买的铅笔总数,从而加速整个计算过程。

当`a == 0`时,表示钢笔的价格经过取模后为0,即铅笔价格是钢笔价格的整数倍。这种情况下,钢笔的价格对计算铅笔的购买数量不再有任何影响,因此可以直接计算剩余金额`b`能购买多少铅笔,并将结果乘以总的钢笔购买方案数(包括不购买钢笔的情况,即`n + 1`)。这样可以直接得到总的购买组合数,无需进一步递归,从而简化了计算过程。

这个表达式利用了递归来交换钢笔和铅笔的角色,从而减少计算的复杂度。`m`代表在当前剩余金额和价格条件下,最多可以购买的铅笔数量。`m * n`计算了在买了m支铅笔的情况下所有可能的钢笔购买方案。然后使用`f(c, c - b - 1, a, m-1)`递归计算在钢笔和铅笔价格交换,且金额调整后的剩余情况中,不重复计算已经考虑过的组binations。这种方法通过递归处理和角色交换,允许算法在较小的搜索空间内进行,从而优化了性能并减少了重复计算。