凑纸币时间复杂度O(1)-python

参加面试的时候看到这道题,当时并没有做出来,很遗憾,功底不够。存了很久一直没发,花了一下午时间做完。

现有i张十元纸币,k张五元纸币,j张两元纸币,购物后要支付n元(i,j,k,n 为整数)。要求编写一个复杂度为O(1)的函数find_solution(i,j,k, n),功能是计算出能否用现在手上拥有的纸币是否足够并能刚好拼凑齐n元,而不需要找零。

首先时间复杂度为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

你可能感兴趣的:(面试题)