每日一题:奇妙货币问题 | 豆包MarsCode AI刷题

206 阅读5分钟

每日一题:奇妙货币问题 | 豆包MarsCode AI刷题

问题描述

小R住在一个名为 X 国的国家,这里的货币非常特殊,面值为 V0,V1,V2,...,Vn,并且 n 可以无限大。该国的交易规则也很特别:在一次交易中,双方只能对每种面值的货币使用不超过两次。

例如,小R想买一件价格为 198 的物品,货币的基数 V=10 时,小R可以使用 2 张 100(102)100(102) 的纸币,卖家则找回 2 张 1(100)1(100) 的纸币。由于这个奇怪的规则,很多 X 国人都无法快速判断某个物品是否可以用这种方式交易成功,他们常常会请聪明的你来帮助。

你能帮他们判断一下,是否能按照规则用给定的货币面值 V 来完成价格为 W的交易吗?


测试样例

样例1:

输入:V = 10,W = 9 输出:'YES'

样例2:

输入:V = 200,W = 40199 输出:'YES'

样例3:

输入:V = 108,W = 50 输出:'NO'

方法一:动态规划法


1. 特殊情况处理

如果 V=1,因为可以组合出任何整数金额,支付和找零一定满足条件,直接返回 "YES"


2. 计算货币面值范围

对于面值为 V的货币,考虑其可能的幂次 V^i,直到 V^i > 2W 为止。 这一部分使用一个 while 循环来生成满足 Vi≤2W 的所有面值。


3. 动态规划初始化

创建一个布尔数组 dp,其索引表示金额(范围为 0,2W),dp[j] = True 表示金额 j 可以用这些货币组合出来。 初始状态 dp[0] = True,因为金额 0 可以用 0 张货币构成。


4. 动态规划更新

对于每个面值 d:

  • 从 2W 到 d遍历金额 j(从大到小遍历以避免重复使用同一面值)。
  • 如果 dp[j−d]=True,说明金额 j−d可达,那么可以用 d面值的货币组合出 j,令 dp[j] = True
  • 如果 j≥2 d 且 dp[j−2d]=True,说明金额 j−2d 可达,那么可以用两张 d 面值的货币组合出 j,也令 dp[j] = True

5. 检查支付和找零的条件

遍历所有可能的支付金额 PP(范围为 W,2W):

  • 如果 dp[P]=True,说明金额 PP 可以支付;
  • 如果 dp[P−W]=True,说明金额 P−W 可作为找零。

如果同时满足以上两个条件,则返回 "YES"


6. 如果没有满足条件的情况

遍历结束后,若没有找到满足条件的 PP,则返回 "NO"

代码实现:

def solution(V, W):
    if V == 1:
        return "YES"
​
    # 计算最大金额范围
    max_exponent = 0
    while V ** max_exponent <= 2 * W:
        max_exponent += 1
​
    # 生成所有可能的面值
    denominations = [V ** i for i in range(max_exponent)]
​
    # 初始化 DP 数组
    dp = [False] * (2 * W + 1)
    dp[0] = True  # 金额 0 可用 0 张货币构成
​
    # 更新 DP 表
    for d in denominations:
        for j in range(2 * W, d - 1, -1):  # 从大到小遍历
            if dp[j - d]:
                dp[j] = True  # 使用 1 张 d
            if j >= 2 * d and dp[j - 2 * d]:
                dp[j] = True  # 使用 2 张 d
​
    # 检查支付和找零的合法性
    for P in range(W, 2 * W + 1):  # 支付金额至少是 W
        if dp[P] and dp[P - W]:  # 同时满足支付和找零
            return "YES"
​
    return "NO"
​
​
# 测试样例
print(solution(10, 9))      # 输出: YES
print(solution(200, 40199))  # 输出: YES
print(solution(108, 50))    # 输出: NO

方法二:进制转化法

1. 特殊情况 ( V = 1 )

如果 ( V = 1 ),因为可以组合出任何整数金额,支付和找零一定满足条件,因此直接返回 "YES"


2. 判断某金额能否表示

定义了辅助函数 can_represent(x),用于判断一个金额 ( x ) 是否可以按照给定规则使用面值 ( V ) 的货币表示出来:

  • 规则:使用 ( V ) 进制表示金额 ( x ),并检查每一位是否小于等于 2。如果某位大于 2,则表示该金额无法用不超过两张相同面值的货币构造。

  • 逻辑

    • 对 ( x ) 进行 ( V ) 进制分解:

      • 检查 ( x % V ),如果超过 2,则返回 False
      • 将 ( x ) 除以 ( V ),继续检查下一位。
    • 如果所有位数均满足条件,则返回 True


3. 遍历所有支付金额

从 ( W ) 到 ( 2W ) 遍历所有可能的支付金额 ( P ),检查是否满足以下两个条件:

  1. ( P ) 可以用面值 ( V ) 的货币构造,即 can_represent(P)True
  2. 找零金额 ( P - W ) 也可以用面值 ( V ) 的货币构造,即 can_represent(P - W)True

如果找到满足条件的 ( P ),则返回 "YES"


4. 如果所有金额均不满足

遍历结束后,若没有找到满足条件的 ( P ),则返回 "NO"

代码实现:

def solution(V, W):
    if V == 1:
        return "YES"
    
    # 判断一个金额是否可以用 V 规则表示
    def can_represent(x):
        while x > 0:
            if x % V > 2:  # 如果某位超过 2,则无法构造
                return False
            x //= V
        return True
​
    # 遍历可能的支付金额
    for P in range(W, 2 * W + 1):  # 支付金额从 W 到 2W
        if can_represent(P) and can_represent(P - W):
            return "YES"
    
    return "NO"
​
​
# 测试样例
print(solution(10, 9))      # 输出: YES
print(solution(200, 40199))  # 输出: YES
print(solution(108, 50))    # 输出: NO

两种方法对比

方法时间复杂度空间复杂度优势劣势
转换进制法O(log⁡V(W))O(1)简单、快速处理大输入不适合动态约束条件的扩展
动态规划法O(len(denominations)⋅W)O(W)灵活,易扩展复杂规则对大金额和基数效率较低