我们购买了一张空白的 DVD 来录制喜欢的电视节目,并且随机附赠了 20 张数字贴纸,其中每个数字都有两张。我们灵机一动,打算用贴纸来标记新的 DVD 编号。将数字“1”贴到第一个录制的 DVD 上,然后把剩下的 19 张贴纸放进抽屉里保存好。第二天,我们又购买了一张空白的 DVD,又随赠 20 张新贴纸。在录制节目后,接着给 DVD 贴上数字“2”。现在我们不禁产生了疑问,什么时候贴纸会用完,那时我们将无法再标记 DVD 了?这个问题用几行 Python 代码就可以解决吗?请提供能够合理运行时间内能够解决问题的代码。
2. 解决方案
这个特别是数字“1”和其它数字之间存在差异,数字“1”比其它数字先用完。算法可以如下进行优化:
- 优化1:设定数字“1”会先用完(很容易证明这一点)
如果优化1成立,那么数字列表将变成:
NUMBERS = [1]
否则,数字列表将是:
NUMBERS = range(10)
下面是函数 how_many_have,用于计算给定数字 n 和贴纸数量 stickers 时,可以使用的贴纸数量。
def how_many_have(dight, n, stickers):
return stickers * n
接着我们定义一个缓存,用于存储数字 dight 和 n 的键值对,以优化计算。
cache = {}
函数 how_many_used 用于计算给定数字 n 时,已经使用了多少个贴纸。如果 (dight, n) 在缓存中,那么直接返回缓存中的值。否则,开始计算。如果 dight 是“0”,那么根据优化策略,直接返回 0。否则,如果 n 大于或等于 10,那么需要计算 n 的第一位数字是否为 dight,如果为真,则将 n 的第一位数字移除,并计算剩余数字的贴纸使用情况。否则,如果 n 的值大于或等于 dight,则将 n 减去 dight,并计算剩余数字的贴纸使用情况。如果 n 的值小于 dight,那么使用贴纸的数量为 0。
def how_many_used(dight, n):
if (dight, n) in cache:
return cache[(dight,n)]
result = 0
if dight == "0":
if OPTIMIZE_1:
return 0
else:
assert(False)
#TODO
else:
if int(n) >= 10:
if n[0] == dight:
result += int(n[1:]) + 1
result += how_many_used(dight, str(int(n[1:])))
result += how_many_used(dight, str(int(str(int(n[0])-1) + "9"*(len(n) - 1))))
else:
result += 1 if n >= dight else 0
if n.endswith("9" * (len(n)-4)): # '4' constant was pick out based on preformence tests
cache[(dight, n)] = result
return result
函数 best_jump 用于计算从数字 i 到数字 i+1 时,需要使用多少张贴纸。
def best_jump(i, stickers_left):
no_of_dights = len(str(i))
return max(1, min(
stickers_left / no_of_dights,
10 ** no_of_dights - i - 1,
))
最后,函数 solve 用于计算贴纸用完时的 DVD 编号。
def solve(stickers):
i = 0
stickers_left = 0
while stickers_left >= 0:
i += best_jump(i, stickers_left)
stickers_left = min(map(
lambda x: how_many_have(x, i, stickers) - how_many_used(str(x), str(i)),
NUMBERS
))
return i - 1
通过调用 solve 函数,我们可以得到不同数量贴纸时,贴纸用完时的 DVD 编号。
for stickers in range(10):
print '%d: %d' % (stickers, solve(stickers))
代码例子
完整的代码如下:
OPTIMIZE_1 = True # we assum that '1' will run out first (It's easy to prove anyway)
if OPTIMIZE_1:
NUMBERS = [1]
else:
NUMBERS = range(10)
def how_many_have(dight, n, stickers):
return stickers * n
cache = {}
def how_many_used(dight, n):
if (dight, n) in cache:
return cache[(dight,n)]
result = 0
if dight == "0":
if OPTIMIZE_1:
return 0
else:
assert(False)
#TODO
else:
if int(n) >= 10:
if n[0] == dight:
result += int(n[1:]) + 1
result += how_many_used(dight, str(int(n[1:])))
result += how_many_used(dight, str(int(str(int(n[0])-1) + "9"*(len(n) - 1))))
else:
result += 1 if n >= dight else 0
if n.endswith("9" * (len(n)-4)): # '4' constant was pick out based on preformence tests
cache[(dight, n)] = result
return result
def best_jump(i, stickers_left):
no_of_dights = len(str(i))
return max(1, min(
stickers_left / no_of_dights,
10 ** no_of_dights - i - 1,
))
def solve(stickers):
i = 0
stickers_left = 0
while stickers_left >= 0:
i += best_jump(i, stickers_left)
stickers_left = min(map(
lambda x: how_many_have(x, i, stickers) - how_many_used(str(x), str(i)),
NUMBERS
))
return i - 1
for stickers in range(10):
print '%d: %d' % (stickers, solve(stickers))