【算法】【动态规划篇】第4节:硬币找零问题

本期任务:介绍算法中关于动态规划思想的几个经典问题

【算法】【动态规划篇】第1节:0-1背包问题

【算法】【动态规划篇】第2节:数字矩阵问题

【算法】【动态规划篇】第3节:数字三角形问题

【算法】【动态规划篇】第4节:硬币找零问题

【算法】【动态规划篇】第5节:剪绳子问题


一、问题描述

    """
    问题介绍
        给定指定的硬币种类,面值为 1, 3, 5(在此具体化些),给定所找零的钱数 sum,给出最少的硬币找零数,每个种类的硬币无限使用。

    问题分析
        看到这问题,当时我想到用贪心算法来求解,最后求解方案因为巧合对了,后来在网上看到动态规划的题目,才知道贪心算法得不到最优解,
        比如 给定 面值为 1, 3, 4,给定找零数为 6,用贪心法得出方案 [4,1,1],但显然 [3,3] 方案即可。
        分析下问题,想一下若存在最少硬币数 num 满足当前给定的找零数 sum,则是不是一定存在最少硬币数 num-1 满足找零数 sum - (1, 3, 5 )?
        答案是存在,想一想就知道,当然边界除外。这个类似于最短路径的 dijkstra算法。

    输入:
    w = [1, 3, 4]
    s = 6

    输出:
    2
"""

二、算法思路

本题的解法与剪绳子问题大同小异,可以阅读【算法】【动态规划篇】第5节:剪绳子问题,加深对此类问题的理解。

1. 策略选择

一个模型:

  • 硬币找零问题是典型的“多阶段决策求最优解”问题,每一次决策有最多有m种选择(m为币值种类数量),直到目标值为0或者找零失败时结束;最优解是最少的找零次数。

三个特征:

  • 重复子问题:

    • 不同的决策序列,到达某个相同的阶段时,可能会产生重复的状态。
    • 本题中,找3零个1元或者1个三月,都可以到达目标值为3元的状态。
  • 无后效性:

    • 前面阶段的状态确定之后,不会被后面阶段的决策所改变。一般而言,满足多阶段决策最优解模型的问题都满足无后效性,特例情况,如八皇后问题解数独问题等。
  • 最优子结构:

    • 后面阶段的状态可以通过前面阶段的状态推导出。
    • 本题中,目标值为6的状态可以由目标值为5元、3元、2元的状态中的最小值加1来计算。

综上所述,本问题满足一个模型、三个特征,所以可以使用动态规划来求解。
当然,凡是能用动态规划解决的问题,都可以用回溯思想来暴力求解,具体实现代码如有兴趣可自行编写,过程不难,更多关于回溯思想的应用,可以参照:【算法】【回溯篇】第7节:0-1背包问题


2. 动态规划算法思路

动态规划使用的流程:自顶向下分析问题,自底向上解决问题!

  • 使用长度为s的一维数组来记录不同目标值下的最少找零数。
  • 更新过程(状态转移思路):
    • 若存在最少硬币数 num 满足当前给定的找零数 sum,则一定存在最少硬币数 num-1 满足找零数 sum - (1, 3, 4)
      d p _ a r r [ i ] = 1 + m i n ( d p _ a r r [ i w [ 0 ] ] , d p _ a r r [ i w [ 1 ] ] , . . . , d p _ a r r [ i w [ n 1 ] ] ) dp\_arr[i] = 1 + min(dp\_arr[i-w[0]], dp\_arr[i-w[1]], ... , dp\_arr[i-w[n-1]])

三、Python代码实现

1. 动态规划解法

class Solution():

    def coins(self, w, s):
        """
        使用动态规划求解硬币找零问题,若存在最少硬币数 num 满足当前给定的找零数 sum,则一定存在最少硬币数 num-1 满足找零数 sum - (1, 3, 4)
        """

        res = [s + 1] * (s)  # 用来记录不同问题规模下的最优解
        for v in w:
            if v <= s:
                res[v - 1] = 1  # 找零数恰好为w中的元素,则num为1

        for i in range(s):  # 依次遍历res,进行填表
            if res[i] == s + 1:  # 只遍历未填写的位置
                min_v = [res[i - v] for v in w if i - v >= 0]
                if min_v:  # 跳过无法找零的情形
                    res[i] = 1 + min(min_v)

        if res[-1] > s:  # 找零失败
            return
        else:
            return res[-1]


def main():
   
    w = [1, 3, 4]
    s = 6
    client = Solution()
    print(client.coins(w, s))


if __name__ == '__main__':
    main()

运行结果:

2
原创文章 36 获赞 32 访问量 2736

猜你喜欢

转载自blog.csdn.net/weixin_43868754/article/details/105715797