每日一题:奇妙货币问题 | 豆包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 ),继续检查下一位。
- 检查 ( x % V ),如果超过 2,则返回
-
如果所有位数均满足条件,则返回
True。
-
3. 遍历所有支付金额
从 ( W ) 到 ( 2W ) 遍历所有可能的支付金额 ( P ),检查是否满足以下两个条件:
- ( P ) 可以用面值 ( V ) 的货币构造,即
can_represent(P)为True; - 找零金额 ( 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(logV(W)) | O(1) | 简单、快速处理大输入 | 不适合动态约束条件的扩展 |
| 动态规划法 | O(len(denominations)⋅W) | O(W) | 灵活,易扩展复杂规则 | 对大金额和基数效率较低 |