基于python的动态规划经典问题(爬楼梯,取珠宝,最大子序列和,找零钱)

1、什么是动态规划

动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。

使用动态规划特征: 
1. 求一个问题的最优解 
2. 大问题可以分解为子问题,子问题还有重叠的更小的子问题 
3. 整体问题最优解取决于子问题的最优解(状态转移方程) 
4. 从上往下分析问题,从下往上解决问题 
5. 讨论底层的边界问题

动态规划最重要的有三个概念:1、最优子结构 2、边界 3、状态转移方程

2、走楼梯问题

有十个台阶,从上往下走,一次只能走一个或两个台阶,请问总共有多少种走法?

1、最优子结构:我们来考虑要走到第十个台阶的最后一步,最后一步必须走到第八或者第九。不难得到 f(10) = f(9)+f(8)。f(9) = f(8)+f(7)

2、边界:f(1) = 1, f(2) = 2

3、状态转移:f(n) = f(n-1) + f(n-2)

解法一:递归

def get_count(n):
    if n == 1:return 1
    if n == 2:return 2
    else:
        return get_count(n-1)+get_count(n-2)
print(get_count(10))

程序简单,但是非常暴力! 

程序复杂度分析:

很显然这是一个满二叉树,高度为N-1。所以总节点数为2^N-1,时间复杂度为O(2^N) 。看着就恐怖。

解法二、备忘录算法

回顾一下上面的递归计算方法,我们不难看出,本来总共只有f(1)-f(N)个节点,硬生生被增加到2^N-1个,这就是产生了大量重复的运算。找到问题的根源,对应的解决方法就应运而生,那就是从下往上算,把以前计算过的数值,保存在一个哈希表中,然后后面计算时先查询一下,存在就无需计算。时间复杂度为O(n) ,空间复杂度为O(n)。但是在仔细一想其实,无需保存所有的f,每个f都只与前两个值相关,所以空间复杂度可以降低为O(1).我们来看看相关代码。

复制代码

def get_count(n):
    if n == 1:return 1
    elif n == 2 :return 2
    else:
        l = [1,2]
        for i in range(3,n):
            l[0],l[1] = l[1],l[0]+l[1]
        return l[0]+l[1]

复制代码

解法3 动态规划的状态转移

第 i 个状态的方案数和第 i-1, i-2时候的状态有关,即:dp[i]=dp[i-1]+dp[i-2],dp表示状态矩阵。

def climb_stairs(n):
    dp=[0]*n
    dp[0]=1
    dp[1]=2
    for i in range(2,n):
        dp[i]=dp[i-1]+dp[i-2]
    return dp[n-1]

3、整数拆分

复制代码

def func(n):
    l = [1]
    for i in range(3,n+1):
        m = 0
        for j in range(1,i//2+1):
            m = max(m,j*(i-j),j*l[i-j-2])
        l.append(m)
    return l
print(func(10))

复制代码

 

 4、最大子序和

核心思想:记录以前一个数结尾的最长子序列的最大值。

 

复制代码

def max_subarry(nums):
    m = nums[0]
    tem_m = nums[0]
    pre = nums[0]
    for i in range(1,len(nums)):
        if pre<=0:
            tem_m = nums[i]
            pre = nums[i]
        else:
            pre+=nums[i]
            tem_m+=nums[i]
        if tem_m>m: m = tem_m
    return m

复制代码

5,取珠宝问题

一条直线上,有n 个房间,每个房间数量不等的财宝,一个盗贼希望从房屋中盗取财宝,由于房屋有警报器,同时从相邻两个房间盗取珠宝就会触发警报,求在不触发警报的情况下,最多可获取多少财宝?

如有6个房间,每个房间珠宝数量为[5,2,6,3,1,7]

状态转移:

前1个房间可获取珠宝的最大数量:5;

前2个房间可获取珠宝的最大数量:5;

前3个房间可获取珠宝的最大数量:11;

前4个房间可获取珠宝的最大数量:11;

前5个房间可获取珠宝的最大数量:12;

可以发现,前i个房间最大可获取的数量和前i-1,i-2个房间可获取的最大珠宝数量,以及第i个房间的珠宝数量有关:第i个房间珠宝有两种方案,获取(总最大数量等于第i个房间的珠宝数量+前i-2个房间的珠宝最大数量)和不获取(总最大数量=前i-1个房间可获取珠宝的最大数量)。

即:dp[i]=max(dp[i-2]+value[i],dp[i-1]),dp表示状态矩阵。

def stealJewellery(value):
    n=len(value)
    dp=[0]*n
    dp[0]=5
    dp[1]=5
    for i in range(2,n):
        dp[i]=max(dp[i-2]+value[i],dp[i-1])
    return dp[n-1]

value=[5,2,6,3,1,7]
print(stealJewellery(value))  

6,最大子序列和问题

给定一个数组,求这个数组的连续子数组中,最大的那一段的和。

如数组 arr= [-2,1,-3,4,-1,2,1,-5,4]

状态转移:

 如果当前数组的值arr[i]加上第i-1个状态的值大于数组arr[i],第i个状态的值就等于arr[i]+dp[i-1],

否则,dp[i]=arr[i]。其中dp[i]记录的是以第i个数组结尾的最大字段和

记录当前最大的子序列和。即 dp[i]=max(dp[i-1]+arr[i],arr[i]),最大子序列和为max(dp)

def subsequenceSum(arr):
    n=len(arr)
    dp=[0]*n
    dp[0] = arr[0]
    for i in range(1,n):
        dp[i]=max(dp[i-1]+arr[i],arr[i])
    return max(dp)

7,找零钱问题

已知不同面值的钞票,求如何用最少数量的钞票组成某个金额,求可以使用的最少钞票数量。如果任意数量的已知面值钞票都无法组成该金额,则返回-1

假设coins=[1,2,5,7,10],金额:amount=14,dp[i]表示金额 i 的最优解。

金额14的最后一张面额可能由coins里面的任意一张得到,即:

14=coins[0]+(14-coins[0]) 14=1+13  dp[14]=1+dp[13]

14=coins[0]+(14-coins[1]) 14=2+12  dp[14]=1+dp[12]

14=coins[0]+(14-coins[2]) 14=5+9  dp[14]=1+dp[9]

14=coins[0]+(14-coins[3]) 14=7+7  dp[14]=1+dp[7]

14=coins[0]+(14-coins[4]) 14=10+4  dp[14]=1+dp[4]

具体最后一张选了哪个面额,就要看哪个面额情况下总拼凑数量最少。即:dp[i]=min(dp[i],dp[i-coins[j]]+1),当计算到金额14时候,小于14的金额的最小数量都已经求出并存储在dp[i]里面,此时只需要直接取出比较即可。最终函数返回dp[14]即为所求的最少数量。

 

求解代码一共有两个for循环,一个是金额从0-amount的最少数量(dp[i]),另外一个是面额数量组合(coins[j])。dp[i]的初始值都设置为amount+1,当所有的coins都无法拼凑当前金额 i 的时候,dp[i] = amount+1 此时dp[i] 大于amount,所以函数返回-1。

def coinChange(coins,amount):
    dp=[amount+1]*(amount+1)
    dp[0]=0
    for i in range(1,amount+1):
        for j in range(len(coins)):
            if coins[j]<=i:
                dp[i]=min(dp[i],dp[i-coins[j]]+1)
    if dp[amount]>amount:
        return -1
    else:
        return dp[amount]

coins=[1,2,5,7,10]
print (coinChange(coins,14))

相关参考:

https://msd.misuland.com/pd/3148108464148973242 

发布了117 篇原创文章 · 获赞 415 · 访问量 55万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章