参加面试的时候看到这道题,当时并没有做出来,很遗憾,功底不够。存了很久一直没发,花了一下午时间做完。
首先时间复杂度为O(1),即无论参数如何变化,函数运行时间不变,这里就否定了通过三层循环找到结果。
其次,这里只需要刚好拼凑齐n元,不用计算有多少种方法。
这里想到可以采用借位的方法。
首先这里要找三个规律:
n的个位数是 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
对应的2元纸币张数的个位数为 | 0,5 | 3,8 | 1,6 | 4,9 | 2,7 | 0,5 | 3,8 | 1,6 | 4,9 | 2,7 |
对应的5元纸币张数的个位数为 | 偶数 | 奇数 | 偶数 | 奇数 | 偶数 | 奇数 | 偶数 | 奇数 | 偶数 | 奇数 |
对应的10元纸币张数的个位数为 | 0-9 | 0-9 | 0-9 | 0-9 | 0-9 | 0-9 | 0-9 | 0-9 | 0-9 | 0-9 |
首先从测试代码写起,通过最简单的三重循环找到所有以当前r2,r5,r10可以满足的数值:
def sham1(r2,r5,r10):
b = set(2*i+5*j+10*k for k in range(r10+1) for j in range(r5+1) for i in range(r2+1))
return b
接下来是分析,之前的规律表说明数字的个位数决定了最小的2元纸币和5元纸币的张数,因为只有这两个数值相加才能凑出1,3,7,9的个位数,即:
rs2 = [0, 3, 1, 4, 2] # 0,3,1,4,2 以5为步长。
small2 = rs2[tar % 10-5 if tar % 10 - 5 >= 0 else tar % 10]
这里可以判断一次,即如果2元纸币的张数不够最小值则返回false,必然不能得到目标数值
需要满足需求的最小的五元纸币的张数为small5 = tar % 2
,如果5元纸币的张数不够最小值则返回false,必然不能得到目标数值,5元纸币最小值是0或者1
然后依次减掉对应的small。
首先减掉small2,得到times5:
count2,count5,count10 = 0, 0, 0
times5 = tar-small2*2 # 这里得到的times5必然是5的倍数
count2 += small2
r2 = r2-count2
if times5 <= 0: # times5小于0则必然无法得到对应数值
if times5 == 0:
return count2,count5,count10
else:
return False
这里times5小于0 的情况是tar为个位数的情况。
然后减去small5,得到times10:
times10 = times5 - small5*5 # 这里得到的times10必然是10的倍数
count5 += small5
r5 = r5 - count5
if times10 == 0: # 这里的times10 不可能小于0
return count2,count5,count10
这里times10不可能小于0。
然后是判断了。
直接看代码:
def find(r2,r5,r10,tar):
sum = r2 * 2 + r5 * 5 + r10 * 10
if tar > sum:
return False
rs2 = [0, 3, 1, 4, 2] # 0,3,1,4,2 以5为步长。
small2 = rs2[tar % 10-5 if tar % 10 - 5 >= 0 else tar % 10] # 2元纸币个数的个位数 tar%10 - 5这个必须大于零
if r2 < small2: # 如果2元纸币的张数不够最小值则返回false,必然不能得到目标数值
return False
small5 = tar % 2
if r5<small5: # 如果5元纸币的张数不够最小值则返回false,必然不能得到目标数值,5元纸币最小值是0或者1
return False
count2,count5,count10 = 0, 0, 0
times5 = tar-small2*2 # 这里得到的times5必然是5的倍数
count2 += small2
r2 = r2-count2
if times5 <= 0: # times5小于0则必然无法得到对应数值
if times5 == 0:
return count2,count5,count10
else:
return False
times10 = times5 - small5*5 # 这里得到的times10必然是10的倍数
count5 += small5
r5 = r5 - count5
if times10 == 0: # 这里的times10 不可能小于0
return count2,count5,count10
# 这里开始做判断,从 10 开始借位,10不够借两个5,5不够借5个2,2不够则无法得到对应数值
if r10*10>=times10: # 10的个数就已经满足需求
count10 = times10//10
return count2,count5,count10
else: # 10的个数就不满足需求
times10 = times10-r10*10
count10 += r10
# 此时times10为减去不够的量 接下来向5借,2为步长。不够再向2借
bor5 = times10//5 # bor5必然是偶数
if bor5 <= r5-count5: # 别忘了之前用过r5,满足则可以得到对应数值,不可以则向2借
count5 = count5+bor5
return count2,count5,count10
else:
# 这里要注意r5的奇偶
mid = r5-1 if r5%2 else r5
times10 = times10 - mid*5
count5 += mid
r5 -= mid
bor2 = times10//2
if bor2<=r2: # 如果向2借可以满足,则则可以得到对应数值,不可以则返回false
count2 += bor2
return count2,count5,count10
else:
return False
测试代码:
import datetime
now = datetime.datetime.now()
for i in range(8500+1):
find(500, 500, 500, i, s)
findo1 = (datetime.datetime.now()-now).total_seconds()
print(s)
now = datetime.datetime.now()
s2 = sham1(500, 500, 500, 8500)
shamo2 = (datetime.datetime.now()-now).total_seconds()
print(s2)
print(findo1,shamo2)
print(s2==s)
输出:(没有打印set,因为太长了,而且浏览器会变得很卡,有需求的可以去试一试,无论数值怎么增加,时间不会超过0.05秒)
0.015639 24.940049
True