动态规划入门

动态规划问题作为一类重要的问题, 经常会困扰我们(其实是困扰我)。这里选择了几个平时经常遇到的动态规划问题, 并给出基础解法(有时候并不是最优解法),只是为了提供一个解题思路。存在更优的解法,也欢迎相互交流。

解决动态规划的问题的最重要的两点:初始条件 和 状态转移方程(重中之重)

1、首先是求矩阵的最短路径问题。假设只能向右和向下两个反向运动。不能对角线运动。

这一问题我们最直接的想法是通过暴力搜索的方法解决。但是暴力遍历的方法在很多的时候是无法满足时间复杂度要求的。我们可以考虑一下,为什么暴力搜索方法时间复杂度这么高呢。原来是暴力搜索方法在计算的时候很多步骤都计算了很多次,多做了很多的无用功。那我们可不可以保存中间的计算结果呢?这就引出了动态规划的方法,它正是在计算的过程中不断保存中间结果来减少计算复杂度的。通常情况下,我们使用的是二维数组来构建动态规划数组,这也不是绝对的。也可能使用一维数组(下面的找零钱,最少硬币使用的就是一维数组)等等。对于最短路径问题,直接上代码看吧:

#矩阵最小路径问题
def min_length(arr, rows, cols):
    dp = [[0 for _ in range(cols)] for _ in range(rows)]    # 初始化dp矩阵
    dp[0][0] = arr[0][0]
    # 初始化第一行     # 第一行和第一列只能有一种方法到达(通过行或者列的累加),所以先初始化
    for i in range(1, cols):
        dp[0][i] = dp[0][i-1] + arr[0][i]
    # 初始化第一列
    for j in range(1, rows):
        dp[j][0] = dp[j-1][0] + arr[j][0]
    
    for i in range(1, rows):
        for j in range(1, cols):
            dp[i][j] = min(dp[i-1][j], dp[i][j-1])+arr[i][j]    # 由于只有两种方法能到达(i,j),状态转移方程只能是两个方向上较短的一条路径加上当时位置的长度。依次类推......
    return dp[rows-1][cols-1]

我们测试一下结果:当矩阵为[[2,3,4,1], [2,3,2,2], [1,2,6,3], [5,7,2,1]]时,输出的结果是15.

2、找零钱问题

找零钱问题其实有两个子问题:1)零钱的找零种类问题   2)最少找零数目的问题

# 找零钱的种类有多少种
def change_num(arr, money):
    n = len(arr)
    m = money
    dp = [[0 for _ in range(m+1)] for _ in range(n)]   # 构成dp矩阵,列表示需要找零的钱的总数,行表示各种零钱的面值
    for i in range(n):
        dp[i][0] = 1
    for j in range(m+1):
        if m%arr[0] == 0:
            dp[0][j] = 1   # 使用如干个arr[0]就可以组成
    for i in range(1, n):
        for j in range(1, m+1):
            temp = 0
            k = 0
            while k*arr[i]<=j:
                temp += dp[i-1][j - k*arr[i]]
                k += 1
            dp[i][j] = temp
    return dp[n-1][m]

对于问题1),最主要是理解一点,当找零数量为j时,如何计算总的找零可能搭配,即总的数目?

我们这样思考,d[i][j]表示,使用前i+1种零钱(从0开始)组成j(零钱总数)的可能情况数目。首先假设在找零的时候不使用arr[i],这时候d[i][j] = d[i-1][j],当然我们也可以在前面的某一个基础上使用k个面值为arr[i]的零钱,只要k*arr[i]不大于j就可以。我们把这些情况都加在一起就得到了结果d[i][j] = d[i-1][j] + d[i-1][j - k*arr[i]]  (k = 1、2、3......, k*arr[i]<=j).

得到状态转移方程就好办了,我们就可以很快的写出上面的代码。我们来验证一下:输入:[2,3,5], 12    输出:9

# 最少硬币的个数,没有考虑不能找零的情况
def change_min_num(arr, money):
    m = money
    dp = [0 for _ in range(m+1)]
    for i in range(m+1):
        MIN = i
        for coin in arr:
            if (coin<=i) and (dp[i - coin]+1<MIN):
                MIN = dp[i - coin]+1
        dp[i] = MIN
    return dp[m]

对于问题2)我们先给出栗子:还是使用上面的输入:[2,3,5], 12

当要找的零钱数目为0时, d[0] = 0; 当要找的零钱数目为1时,由于所有的零钱面值都大于1,所以d[1] = 1(由于上面的代码MIN=i确定, 我们也可以设置一个很大的正整数);当零钱数目为money=2时,零钱中存在2<=money, 所以d[2] = d[0]+1,即使用一张面值为2的零钱;当money==3的时候,零钱2,3都小于等于money,所以d[3] = min{d[1]+1, d[0]+1}, d[0]+1表示使用一张面值为3的零钱,d[1]+1表示在money==1的基础上使用一张面值为2的零钱。并选择这两者中的较小者......依次类推。就得到了最后的结果: d[12] = 3,  (5+5+2=12)

----------------------先写两个,下次再继续写------------------------------

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值