文章目录
- 前言
- 0、题目
- 一、暴力总是不能解决问题的
- 二、还能更暴力一点
- 三、减少暴力思想
- 四、引入先进思想
- 总结
前言
这题挺有意思的,典型的背包组合问题,虽然没有要求各种组合方式,不过我们可以试试给出组合方式。当然这题不太可能用一行代码解决了…来看一行超人的可别失望了~ 本文满满的干货!写了两天呢!
先上100分图:
提示:以下是本篇文章正文内容,下面案例可供参考
0、题目
题目描述:
有1分,2分,5分,10分四种硬币,每种硬币数量无限,给定n分钱(n<100000),有多少中组合可以组成n分钱?
输入描述:
输入整数n.(1<=n<=100000)
输出描述:
输出组合数,答案对1e9+7取模。
一、暴力总是不能解决问题的
老规矩,遇到问题先暴力一波!
代码如下(示例):
def force(n):
res = 0
for i in range(n+1):
for j in range(n//2+1):
for k in range(n//5+1):
for l in range(n//10+1):
if i + j*2 + k*5 + l*10 == n:
res += 1
return res
if __name__ == "__main__":
n = 1000
result = force(n)
print(result)
多么的错落有致的代码!太优美了。可惜好看归好看,好写也是真好写,n=100时,它费了一秒钟就算出来2156了!但当n=1000时,它就算不出来了!这破代码幼儿园小朋友扳手指头都比它强!丢远点~
二、还能更暴力一点
明显可以模拟实际情况一个个取硬币来实现,最多有四种面额,就循环四次,每次取一个,不够值就递归去取,一直取到n==0,就是一种方法,添加到列表。想法很好!
代码如下(示例):
def reduce(n, path=[]):
coins = [10, 5, 2, 1]
res = [] # 初始化结果列表
if n == 0: # 如果n为0,说明已经找到了一种方案
res.append(path) # 将这个方案添加到结果列表中
return res # 返回结果列表
for coin in coins: # 遍历硬币面额
if n >= coin: # 如果当前硬币面额小于等于n
new_path = path + [coin] # 更新path,加入新取的硬币
res += reduce(n - coin, path=new_path) # 继续递归,更新n和path
return res # 返回结果列表
if __name__ == "__main__":
n = 100
result = reduce(n)
result = [sorted(v) for v in result]
res = []
for x in result:
if x not in res:
res.append(x)
print(res.__len__())
果然能成,n=10的情况,biu一下就算出来11了,因为给出的是各种取法,所以要去重。最后列表len值就是方法数了。可惜正如笔者在博文小议递归:https://blog.csdn.net/alal001/article/details/130436551中所写,这种干重复活的递归都是不靠谱的!这破玩意连n=100都算不出来!我也算不出这玩意的时间复杂度了…
三、减少暴力思想
很明白的看出,越暴力越不能解决问题!
递归是可以拆解的,我们就就把它拆简单点,毕竟计算机是机械的执行命令的。咱也不要硬币的组成的方式了,数字大了真顶不住的!
稍微想想就知道,只用1分的硬币,组成任何数都只有一个办法。
只用2分和1分的硬币,不计全1分的情况就是n//2个情况。
哈~ 咱是正经小学毕业的!算不出来用1,2,5三种的情况了。没关系这部分交给计算机算。
代码如下(示例):
def only_125(n): # 这是只用1、2、5的情况
only_12 = lambda n: n//2 # 这是只用2的情况
res = 0
while n > 0:
if n < 5:
res += only_12(n)
break
else:
res = res + 1 + only_12(n)
n -= 5
return res
def main(n): # 主函数
only_1 = 1 # 这是只用1的情况
res = 0
while n > 0:
if n < 5:
res += n//2
break
elif n < 10:
res = res + 1 + n//2 # 加1是有一个5的情况,n//2是全1、2的情况
n -= 5 # 减掉一个5后,余下转到小于5处理
else:
res = res + only_125(n) + 1
n -= 10
return res + only_1
if __name__ == "__main__":
n = 100000
print(main(n))
这逻辑就够简单明了,而且效率大幅提升! n = 100000 虽然用了几秒,但也算出来了。就还是不能过网页测试…
嗯~ 再把1、2、5三种的组合情况简化一下就行了!但那就成纯数学公式了,体现不了咱的技术水平,嗯~ 主要咱也推导不出那公式…
四、引入先进思想
痛定思痛,咱学习数学去,小学数学老师总是教导我们要找规律:
咱换个想法,靠计算估计只能去解决上面的1、2、5组合的简化方法了。所以数学不好就别计算了,咱画表格!假设存在一张表格,里面记录了各种硬币组成n的数量…咱查表就解决了,这就老快了!
那么问题转移到如何生成这张表格了!也就是如何找规律的问题:
代码如下(示例):
def dpway(n):
dp = [0] * (n + 1) # 初始化dp数组,dp[i]表示组成i分钱的方案数
dp[0] = 1 # 初始化dp[0]为1,因为组成0分钱只有一种方案,即不选硬币
for coin in [1, 2, 5, 10]: # 遍历硬币面额
for i in range(coin, n + 1): # 遍历每个分钱数
dp[i] = (dp[i] + dp[i - coin]) # 计算组成i分钱的方案数
return dp[n] # 返回组成n分钱的方案数
if __name__ == "__main__":
n = 100
#result = force(n)
#result = reduce(n)
result = dpway(n)
print(result)
首先,定义了一个长度为n+1的数组dp,其中dp[i]表示组成面额为i的方案数。然后,将dp[0]初始化为1,因为组成0分钱只有一种方案,即不选硬币。
接下来,遍历硬币面额1、2、5、10四个不同的面额。对于每个硬币面额,使用两个嵌套的循环遍历每个分钱数i,计算组成i分钱的方案数,即dp[i]加上dp[i-coin]。最后,返回dp[n],即组成n分钱的方案数。如:表格会生成dp[1]为1,dp[2]为2…
此动态规划法已经很精简了,理解也有点不能了,如果用二维数组会好理解一点。也因为没有规划过程,所以没法得出每一种方案。
总结
希望本博文能对各位看官理解递归和动态规划有点帮助。