算法设计与分析基础(第三版)之第8章 动态规划

本博客主要是对Anany Levitin著、潘彦译的算法设计与分析基础(第三版)之第8章 动态规划进行代码梳理与讲解,代码语言主要为Python,其他语言也类似,可以把Python的解题方法看成是伪代码进行转换。
动态规划是数据结构中最重要的一环,也是面试中大家觉得头大的一类面试题型。关于动态规划的解题方法可以参考博主另一篇博文:一文详解动态规划【更新ing】。这篇博文的主要目的在于对书中的题目进行代码化。
例题1:币值最大化问题
这个是个经典题目了,也有多种变体,我们先看书中题目。题目是这个样子的:
给定一排 n {n} n个硬币,其面值均为正整数 c 1 , c 2 , . . . , c n {c_1, c_2, ..., c_n} c1,c2,...,cn,这些整数并不一定两两不同。请问如何选择硬币,使得其原始位置互不相邻的条件下,所选硬币总额最大。
该题是一个动态规划的典型题目,重点在于理解原始位置互不相邻及子问题的构建上。整体上,解决动态规划问题四步走:
(1)划分子问题
(2)构建状态转移
(3)确定初始状态
(4)循环迭代求解
我们思考如下:
1、题目理解:
       互不相邻,我们可以理解为下标间隔2,即 1 , 3 , 5 , . . . , 2 n + 1 {1, 3, 5,..., 2n+1 } 1,3,5,...,2n+1 或者 0 , 2 , 4 , . . . , 2 n {0, 2, 4,..., 2n} 0,2,4,...,2n这种方式,原始位置一定互不相邻。
2、划分子问题
       我们自顶向下思考这个问题,如果说我们对于已经选择好的硬币组合 f ( n ) f(n) f(n),代表的含义为我们已经选择好下标到 n n n的硬币组合,此时的总额记为 f ( n ) f(n) f(n),那么对于 f ( n ) f(n) f(n)来说,它的选择计划对于最后一个硬币 c n c_n cn有两个来源:
(1)选择硬币 c n {c_n} cn,因题目要求选择的是互不相邻的位置,那么子问题就变成必须要求 f ( n − 2 ) f(n-2) f(n2)最大,此时 f ( n ) = f ( n − 2 ) + c n {f(n) = f(n-2) + c_n} f(n)=f(n2)+cn
(2)不选择硬币 c n {c_n} cn,此时子问题变成—>如何在 c 1 , c 2 , . . . , c n − 1 {c_1, c_2, ..., c_{n-1}} c1,c2,...,cn1中选择硬币,使得其要求总额最大。即要求 f ( n − 1 ) f(n-1) f(n1)最大就好。
3、构建状态转移
       基于(1)、(2),对于 f ( n ) f(n) f(n)整体上来说,总额最大的关系表达式为:
f ( n ) = m a x ( f ( n − 2 ) + c n , f ( n − 1 ) ) {f(n) = max( f(n-2) + c_n, f(n-1))} f(n)=max(f(n2)+cn,f(n1))
完成了动态规划解题第一步划分子问题和第二步构建状态转移。
3、确定初始状态
       这里初始状态有两个: n = 0 和 n = 1 n=0和n=1 n=0n=1这两个状态。
       当 n = 0 n=0 n=0时,表明此时硬币数组为空,不论如何选择,最大金额一定为0,即 f ( 0 ) = 0 f(0)=0 f(0)=0
       当 n = 1 n=1 n=1时,表明此时硬币数组只有一个元素,而且该元素为正整数。不论如何选择,最大金额一定为该元素,即 f ( 1 ) = c 1 f(1)=c_1 f(1)=c1
4、循环迭代求解
先设定一个长度为 n n n【为什么是 n n n而不是 n + 1 n+1 n+1呢?欢迎评论区讨论~】的数组 a r r arr arr用来装一排 i , 0 < = i < = n i, 0<=i<=n i,0<=i<=n的硬币中选择互不相邻的硬币的最大总金额,那么从一排n个硬币中选择出互不相邻的硬币总金额 a r r [ n ] arr[n] arr[n]

class CoinSelection(object):
    def __init__(self, n, arr_coin):
        self.n = n
        self.arr_coin = arr_coin
    def solution(self):
        if self.n == 0 or self.arr_coin is None:
            return 0
        else:
            arr_solution = [0] * self.n
            arr_solution[0] = 0
            arr_solution[1] = self.arr_coin[0]
            for coin_number in range(2, n, 1):
                arr_solution[coin_number] = max(arr_solution[coin_number-2] + self.arr_coin[coin_number], arr_solution[coin_number - 1] )
            return arr_solution[-1]


if __name__ == "__main__":
    n = 6
    coin_arr = [5, 1, 2, 10, 6, 2]
    coin_solution = CoinSelection(n, coin_arr)
    result = coin_solution.solution()
    print(result)

此种接法的空间复杂度和时间复杂度均为 O ( n ) O(n) O(n)

例题2:找零问题
需找零金额为 n n n,最少要用多少面值为 d 1 < d 2 < . . . < d m d_1<d_2<...<d_m d1<d2<...<dm的硬币?假设 m m m种面值为 d 1 < d 2 < . . . < d m d_1<d_2<...<d_m d1<d2<...<dm的硬币数量无限可得, d 1 = 1 d_1=1 d1=1
该题思考:
一开始这个题目博主理解有误,主要在对【找零金额】这四个字的理解上,博主开始理解成了找零钱,也就是数学中的余数,然后就纳闷:这个题目也没告诉博主需要付多少钱啊,零钱怎么找呢?懵逼了一圈后,看了解答发现,这里的找零金额是指总金额。后续解答讲解中博主都用总金额替换掉找零金额。。
假设 f ( n ) f(n) f(n)表示选用最少的硬币组成的总金额为 n n n,上一个阶段为从 m m m个面值硬币中任意选一个,且组成硬币总额为 n − d j n-d_j ndj的所需硬币数最少,即 f ( n ) = m i n f ( n − d j ) + 1 f(n) = min{f(n-d_j)} + 1 f(n)=minf(ndj)+1
初始边界条件: f ( 0 ) = 0 f(0) = 0 f(0)=0 f ( 1 ) f(1) f(1)表示总金额为1,此时只能选择 d 1 d_1 d1,故而只有选择 d 1 d_1 d1这一种方式,因此 f ( 1 ) = 1 f(1) = 1 f(1)=1

import numpy as np


class SumMoney(object):
    def __init__(self, n, coin_value):
        self.n = n
        self.coin_value = coin_value

    def solution(self):
        coin_solution = [0] * (self.n + 1)
        coin_solution[0] = 0
        coin_solution[1] = 1
        for sum_money in range(2, self.n + 1, 1):
            tmp = np.inf
            for single_coin_value_index in range(0, len(self.coin_value)):
                if sum_money >= self.coin_value[single_coin_value_index]:
                    tmp = min(coin_solution[sum_money - self.coin_value[single_coin_value_index]], tmp)
            coin_solution[sum_money] = tmp + 1
        return coin_solution[self.n]


if __name__ == "__main__":
    n = 6
    coin_arr = [1, 3, 4]
    coin_solution = SumMoney(n, coin_arr)
    result = coin_solution.solution()
    print(result)

此时的输出结果为:

2

Process finished with exit code 0

变体1:
输出例题2的具体最优解即最少的硬币组合方案中使用了哪些硬币。
寻找组合的过程与例题2的思路反着来,即先找到最少的硬币组合数,然后从最少硬币组合数的最后一个数字出发,看其所选硬币数,然后再往上反着推。
此题需要对例题2的每一步的最少硬币数用到的硬币进行存储,然后在找到最少硬币后反推着来。

变体2:对于每种面值的硬币限制其使用数量

例题3:最短路径问题

Ref:
1、硬币找零问题–动态规划参考
2、动态规划从入门到专家

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值