动态规划经典问题之硬币的两种解法Python

题目来源于力扣的《面试题 08.11. 硬币》,难度中等,原题如下:

硬币。给定数量不限的硬币,币值为25分、10分、5分和1分,编写代码计算n分有几种表示法。(结果可能会很大,你需要将结果模上1000000007)

示例1:

 输入: n = 5
 输出:2
 解释: 有两种方式可以凑成总金额:
        5=5
        5=1+1+1+1+1


示例2:

 输入: n = 10
 输出:4
 解释: 有四种方式可以凑成总金额:
10=10
10=5+5
10=5+1+1+1+1+1
10=1+1+1+1+1+1+1+1+1+1

思路

由于题目所给的是硬币是无限制的,也就是说这类似于一个完全背包问题(个人拙见,若有不对的地方可在评论区提出);

学习遇到动态规划问题,三大件:1.确定dp含义;2.考虑base case;3.状态转移方程;

1.确定dp数组及及其含义:

        dp[i][j]: 代表当前 种硬币能组成面值为 的方案数;

2.考虑初始状态:

        1)dp[0][j]:即当前0种硬币组成面值为j的方案数,这显然是不可能的,因此为0;

        2)dp[i][0]:即当前i种硬币一起组成面值0的方案数,这是可以的,那就是一个硬币都不给;

3.状态转移方程:

         1)如果当前需要组成的面值大于了当前的硬币 coins[i - 1] ,那么有

                dp[i][j] = dp[i-1][j] + dp[i][j-coins[i-1]]

                其中dp[i-1][j]:表示当前硬币不选,所有需要组成的面值 j i - 1种面值组成;

                dp[i][j-coins[i-1]]:表示当前硬币选了,那么选完当前硬币剩余的需要组成的面额则为 j - coins[i-1]

        2)如果当前需要组成的面值小于当前的硬币,那么有

                dp[i][j] = dp[i-1][j]

                由于当前硬币无法选择,因此当前状态下只能用 i - 1 种硬币去组成面值

代码

class Solution:
    def waysToChange(self, n: int) -> int:
        coins = [1,5,10,25]
        coinsNum = len(coins)                      # 硬币总个数
        mod = 1000000007                           # 取模
        dp = [[0 for i in range(n+1)] for j in range(coinsNum+1)]
        for i in range(1,coinsNum + 1):
            dp[i][0] = 1                           # 任何硬币去凑0面值的方案数都是1 那就是不拿
        for i in range(1,coinsNum + 1):
            for j in range(1,n + 1):
                if j - coins[i-1] >= 0:            # 当目前需要凑的面值大于当前状态的硬币面值时
                    dp[i][j] = (dp[i-1][j] + dp[i][j-coins[i-1]]) % mod
                    # 要么不拿当前硬币 把当前有的所有面值全部交给这个硬币之前的全部面值去凑
                    # 要么就拿当前硬币 然后把当前需要的面值减掉当前的硬币之后剩下的面值让其他硬币去凑
                else:
                    dp[i][j] = dp[i-1][j]  %  mod  # 当前硬币拿不了的情况的方案数
        return dp[-1][-1]

提交结果

 这道题属于中等难度还是有它的道理的,力扣还是特别特别多的大佬存在的(被碾压的不轻了),上面我们采用的是一个二维DP的解法,那么我们接下来尝试一下能否进行复杂度优化,即将二维DP进一步转化成一维DP!!

1.首先,我们从状态转移方程可以观察到:dp[i][j] 仅仅和 dp[i-1] 的状态有关,所以可以考虑采用滚动数组的方式将其压缩一维,用 i 表示当前需要组成的面值,代码如下:

代码

class Solution:
    def waysToChange(self, n: int) -> int:
        coins = [1,5,10,25]
        coinsNum = len(coins)                      # 硬币总个数
        mod = 1000000007                           # 取模
        dp = [0 for i in range(n+1)]               # 一维
        dp[0] = 1                                  # 没有硬币凑0分有1种方案 那就是不用凑
        for coin in coins:
            for i in range(1,n+1):
                if i - coin >= 0:
                    dp[i] = (dp[i] + dp[i-coin])  % mod
        return dp[-1]

 提交结果

可以说,还是可以优化一定的复杂度的!

      如果屏幕前的你有更好的解决方案,希望不吝赐教!感谢,若文章对你有帮助,请留个赞or关注 谢谢喔~

      最后的最后需要注意的问题就是,一定要注意题目有无要求取模,如果状态转移写对了,但是因为这个点过不了评测就很可惜的!!

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 

  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值