详解动态规划算法(Python)

视频来源:https://www.bilibili.com/video/BV1xb411e7ww?from=search&seid=3112459103674479435 

详解动态规划算法(Python)_第1张图片

详解动态规划算法(Python)_第2张图片

动态规划解题四组成部分


1、确定状态
解动态规划的时候需要一个数组,数组的每个元素F[i],或者F[i,j]代表什么需要明确;
确定状态需要两个意识:

1.1 最后一步
k枚硬币a_{1},a_{2},a_{3}...a_{k},面值加起来应该等于27,最后的硬币是a_{k}

详解动态规划算法(Python)_第3张图片
1.2 子问题

除掉最后一枚硬币,前面的k-1枚硬币加起来应该等于27-a_{k},因为是最优策略,所以拼出27-a_{k}的硬币数量也最少,为了简化定义,我们设状态F[x]=最少用多少枚硬币拼出x

2、建立状态转移方程

详解动态规划算法(Python)_第4张图片


3、初始条件和边界情况

什么时候需要人工定义F[0]?  用转移方程算不出来的时候

详解动态规划算法(Python)_第5张图片
4、计算顺序

详解动态规划算法(Python)_第6张图片

详解动态规划算法(Python)_第7张图片

详解动态规划算法(Python)_第8张图片

详解动态规划算法(Python)_第9张图片

5、 例子

5.1:最少硬币数

有3种硬币,其面值为2元、5元、7元,现在要拼成27元,并要求硬币数量最少

备注:每种硬币数量都无穷多

import pandas 
step_coin_select=[]
coins = [2,5,7] #硬币种类面值
amount = 27 #拼出的金额
f=[0]*(amount+1) #f[0]=0为初始条件
#i 为f(1) f(2) f(3)...f(11)
for i in range(1,amount+1):
    # 设定f(i)初始值为正无穷,为拼不出的金额做准备
    f[i]=float("inf")
    for j in range(len(coins)):
        #如果金额小于某种硬币面值
        if i>=coins[j]:
            f[i]=min(f[i],(f[i-coins[j]]+1))
            if f[i]==(f[i-coins[j]]+1):
                temp=['f[{}]'.format(i),(i-coins[j]),coins[j],f[i]]
                step_coin_select.append(temp)
           
step_coin_selectdata=pandas.DataFrame(step_coin_select,columns=['金额','子问题','最后状态','最少硬币数'])
            
step_coin_selectdata=step_coin_selectdata.sort_values(by=['金额','硬币数'],ascending=[1,1])#0降序
#去重             
step_coin_select_dup=step_coin_selectdata.drop_duplicates(['金额'])

5.2:不同路径

给定m行n列的网格,有一个机器人从网格[0,0]处出发,且只能往右或往下走,有多少条路径可以走到右下角?

详解动态规划算法(Python)_第10张图片

 

1)动态规划组成一:状态

 

设f[i,j]为有多少种方式从左上角走到[i,j]

1.1)最后状态:[m-1,n-1]

详解动态规划算法(Python)_第11张图片

1.2)子问题:走到最后状态[m-1,n-1]的前一步是[m-2,n-1]或者[m-1,n-2]

设有X步从左上角走到[m-2,n-1],有Y步从左上角走到[m-1,n-2],那么从左上角走到右下角的方式有X+Y种

详解动态规划算法(Python)_第12张图片

2)动态规划组成二:转移方程

f[i,j]=f[i-1,j]+[i,j-1],f[i,j]为有多少种方式从左上角走到[i,j]

详解动态规划算法(Python)_第13张图片

3)动态规划组成三:初始条件和边界情况

f[0,0]=1  #机器人只要一种方式进入左上角

边界情况:

i=0 或者 j=0时,f[i,j]只要一种方式可以过来

详解动态规划算法(Python)_第14张图片

4)动态规划组成四:计算顺序

f[0,0]=1

然后计算第一行

计算第二行

...

计算m-1行

多少条路径答案:f[m-1,n-1]

时间复杂度(计算步数):O(MN),空间复杂度:O(MN)

详解动态规划算法(Python)_第15张图片

"""
https://leetcode-cn.com/problems/unique-paths/%20/
给定m行n列的网格,有一个机器人从网格[0,0]处出发,且只能往右或往下走,有多少条路径可以走到右下角?
1、状态:
1.1 最后一步:[m-1,n-1]
1.2 子问题:走到最后状态[m-1,n-1]的前一步是[m-2,n-1]或者[m-1,n-2]
设有X步从左上角走到[m-2,n-1],有Y步从左上角走到[m-1,n-2],那么从左上角走到右下角的方式有X+Y种
2、动态规划组成二:转移方程:
f[i,j]=f[i-1,j]+f[i,j-1]
3、初始条件和边界情况:
f[0,0]=1  #机器人只要一种方式进入左上角
边界情况:i=0 或者 j=0时,f[i,j]只要一种方式可以过来
4、计算顺序:从上到下,从左到右
results=[[1]*n]*m遇到bug,
原因是list的浅拷贝问题
list * n—>n shallow copies of list concatenated
n个list的浅拷贝的连接
修改其中的任何一个元素会改变整个列表
改写为循环赋值即可[([0]*n) for i in range(n)]
"""
n=7
m=8
results=[[1]*n for i in range(m)] 
#f[0,j]和f[i,0]的路径显然只有1条,所以跳过
for i in range(1,m):
    for j in range(1,n):
        results[i][j]=results[i-1][j]+results[i][j-1]
        print(i,j,results[i][j])
    print("-"*30)
# 总共有多少条不同的路径?
print(results[m-1][n-1])
        
# leetcode;
# class Solution:
#     def uniquePaths(self, m: int, n: int) -> int:
#         results=[[1]*n for i in range(m)]
#         #f[0,j]和f[i,0]的路径显然只有1条,所以跳过
#         for i in range(1,m):
#             for j in range(1,n):
#                 results[i][j]=results[i-1][j]+results[i][j-1]
                
#         return results[m-1][n-1]

5.3:jump game

详解动态规划算法(Python)_第16张图片

详解动态规划算法(Python)_第17张图片

详解动态规划算法(Python)_第18张图片

详解动态规划算法(Python)_第19张图片

 

详解动态规划算法(Python)_第20张图片

#/*-------------------------------------*/
#/* jump-game
#https://leetcode.com/problems/jump-game/
#/*-------------------------------------*/
a=[2,3,1,1,4]
a=[3,2,1,0,4]
a=[2,0,0]

n=len(a)
flag=True
for i in range(n-1,0,-1):
    #flag为True有两种情况,一个是初始情况最后一步的前一步可以跳到最后一步,一种是上一次的j可以跳到i
    if flag:
        #寻找是否可以从第j步跳到i 步
        for j in range(i-1,-1,-1):
            print(i,j,"比较:",a[j]>=(i-j))
            #如果j可以跳到i
            if (a[j]>=(i-j)):
                i=j
                flag=True
                break
            else:
                flag=False
    print("-"*30)
# flag: Determine if you are able to reach the last index.
print(flag)
    

# leetcode    
# class Solution:
#     def canJump(self, nums: List[int]) -> bool:
#         global flag
#         n=len(nums)
#         flag=True
#         for i in range(n-1,0,-1):
#             #flag为True有2种情况,一个是初始情况最后一步的前一步可以跳到最后一步,一种是上一次的j可以跳到i
#             if flag:
#                 #寻找是否可以从第j步跳到i 步
#                 for j in range(i-1,-1,-1):
#                     #如果j可以跳到i
#                     if (nums[j]>=(i-j)):
#                         i=j
#                         flag=True
#                         break
#                     else:
#                         flag=False
#         return flag

5.4 0-1背包

在M件物品取出若干件放在体积为W的背包里,每件物品只有一件,他们有各自的体积和价值,问如何选择使得背包能够装下的物品价值最多

动态规划的思路:一个一个物品去尝试,一点一点扩大考虑能够容纳的容积的大小,整个过程就像在填写一张二维表格

创建一个dp[M+1,W+1]的二维数据,代表其能够装下的最大价值,其中第一行全是0,代表没有物品放进来,第一列全是0,代表不放东西

  容量
0 1 2 3 4 5 6 7 8 9 10 11 12 13
0 0 0 0 0 0 0 0 0 0 0 0 0 0
物品重量 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1
2 0 1 6 7 7 7 7 7 7 7 7 7 7 7
5 0 1 6 7 7 18 19 24 25 25 25 25 25 25
6 0 1 6 7 7 18 22 24 28 29 29 40 41 46
7 0 1 6 7 7 18 22 28 29 34 35 40 46 50
9 0 1 6 7 7 18 22 28 29 36 37 42 46 50

 

详解动态规划算法(Python)_第21张图片

详解动态规划算法(Python)_第22张图片

import numpy as np
weights=[1,2,5,6,7,9]
price=[1,6,18,22,28,36]
n=len(weights)
print("n:",n) #6
W=13

dp=np.array([[0]*(W+1)]*(n+1))
# 计算顺序:从上往下,一行一行计算
#r代表某件物品
for r in range(1,n+1):
    for c in range(1,W+1):
        #如果第n件物品大于容量c,那么在该容量下放不下该物品,最佳价值等于该容量下前n-1的最大价值
        if weights[r-1]>c:
            dp[r,c]=dp[r-1,c]
        #该容量下能放下该物品,有两种选项,放和不放
        #1、放:放下该物品对应的价值price[r-1]加上放下该物品后剩余容量对应的最大价值dp[r-1,c-weights[r-1]]
        #2、不放:前n-1的最大价值dp[r-1,c]
        else:
            dp[r,c]=max(dp[r-1,c],dp[r-1,c-weights[r-1]]+price[r-1])

5.5 分割等和子集(416)

给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

注意:

每个数组中的元素不会超过 100
数组的大小不会超过 200
示例 1:

输入: [1, 5, 11, 5]

输出: true

解释: 数组可以分割成 [1, 5, 5] 和 [11].

解题思路从0-1背包而来,只是dp[r,c]代表的是在前r个nums有没有和为c,有的话为True,没有的话False

详解动态规划算法(Python)_第23张图片

 

nums=[3,3,3,4,5]
n=len(nums)+1
half=sum(nums)/2+1

half=int(half)
dp=np.array([[0]*half]*n) 
for r in range(1,n):
    for c in range(1,half):
        print(r,c)
        if nums[r-1]==c or dp[r-1,c]==1:
            dp[r,c]=1
        elif  c-nums[r-1]>0 and dp[r-1,c-nums[r-1]]==1:
            dp[r,c]=1
        else:
            pass
import pandas
col=[]
for i in range(half):
    col.append("a{}".format(i))
dp_data=pandas.DataFrame(dp,columns=col)

# leetcode
# import numpy as np
# class Solution:
#     def canPartition(self, nums: List[int]) -> bool:
#         n=len(nums)+1
#         half=sum(nums)/2+1
#         if half%1==0 and n>2:
#             half=int(half)
#             dp=np.array([[0]*half]*n) 
#             for r in range(1,n):
#                 for c in range(1,half):
#                     if nums[r-1]==c or dp[r-1,c]==1:
#                         dp[r,c]=1
#                     elif  c-nums[r-1]>0 and dp[r-1,c-nums[r-1]]==1:
#                         dp[r,c]=1
#                     else:
#                         pass

#         else:
#             return False
#         return dp[r,c]==1

 5.6 买卖股票最佳时机

话说你有一个数组,这个数组代表每天股票的交易价格

现在只允许你买卖一次,即只能买一次卖一次,设计一个算法去寻找最大收益

注意你不能在买入之前卖出。

原题:https://leetcode.com/problems/best-time-to-buy-and-sell-stock/

思路:利用动态规划的思想迭代最小价格和收益

prices=[2,1,2,0,2]
min_value=max(prices)
profit=0
for v in prices:
    if vprofit:
        profit=v-min_value
        
print(profit)

5.7   买卖股票的最佳时机 II (122题)

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

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

prices=[2,1,2,0,1]
length = len(prices)
max_profit, buy, sell = 0, -1, -1
for i in range(length):
    if i==length-1:
        pass
    elif prices[i] int:
#         max_profit, buy, sell = 0, -1, -1
#         length = len(prices)
#         if length>0:
#             for i in range(length):
#                 if i==length-1:
#                     pass
#                 elif prices[i]

5.8 买卖股票的最佳时机 III (123)

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

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

对问题进行定义:dp[i][k][status]表示累计最大利润,其中i表示第i天,k表示已经完成几次买卖交易,status表示状态(0卖出,1买入)                            
                            
dp[i][0][0]:表示第i天交易了0次时卖出后的累计最大利润,对应于初始状态,因为没有买卖所以是0                            
dp[i][0][1]:表示第i天交易了0次时买入后的累计最大利润                            
dp[i][1][0]:表示第i天交易了1次时卖出后的累计最大利润                            
dp[i][1][1]:表示第i天交易了1次时买入后的累计最大利润                            
dp[i][2][0]:表示第i天交易了2次时卖出后的累计最大利润                            
dp[i][2][1]:表示第i天交易了2次时买入后的累计最大利润                            
注意,最后一个dp[i][2][1] 实际是不存在的,因为交易两次后,就不能再买入了。                            
                            
我们定义第1次买卖的dp公式                            
第一次买入,根据昨天权衡后决定的买入和今天买入,对比取最大值                            
dp[i][0][1]=max(dp[i-1][0][1],-prices[i])                            
第一次卖出,可以根据昨天权衡后决定的卖出和今天卖出减去昨天权衡后的买入,对比取最大值                            
dp[i][1][0]=max(dp[i-1][1][0],prices[i]-dp[i-1][0][1])                            
                            
第二次买入,根据昨天权衡后决定的买入和今天买入并加上第一次收益,对比取最大值                            
dp[i][1][1]=max(dp[i-1][1][1],dp[i-1][1][0]-prices[i])                            
第二次卖出,可以根据昨天权衡后决定的卖出和今天卖出减去昨天权衡后的买入,对比取最大值                            
dp[i][2][0]=max(dp[i-1][2][0],prices[i]-dp[i-1][1][1])                            
详解动态规划算法(Python)_第24张图片

import numpy as np
prices=[7,1,5,3,6,4]

#买入卖出2种状态
status=2
#交易2次
k=2
#天数
n=len(prices)

dp=np.array([[[-1]*2]*(k+1) for _ in range(n)])
#设置初始状态
dp[0,0,0]=0 #初始状态,没买入没卖出
dp[0,0,1]=-prices[0] #第一天第1次买入收益为-7
dp[0,1,0]=0  #第一天第1次卖出收益为0,因为没有开始卖
dp[0,1,1]=-prices[0]  #第一天第2次买入收益为-7
dp[0,2,0]=0  #第一天第2次卖出收益为0,因为没有开始卖
dp[0,2,1]=-prices[0]

for i in range(1,n):
    dp[i,0,0]=0 #初始状态,没买入没卖出
    #[i,0,1],第i天第1次买入累计最大收益,[i,1,0],第i天第1次卖出累计最大收益
    dp[i,0,1]=max(dp[i-1,0,1],-prices[i])
    dp[i,1,0]=max(dp[i-1,1,0],prices[i]+dp[i-1,0,1])
    #[i,1,1],第i天第2次买入累计最大收益,[i,2,0],第i天第2次卖出累计最大收益
    dp[i,1,1]=max(dp[i-1,1,1],dp[i,1,0]-prices[i])
    dp[i,2,0]=max(dp[i-1,2,0],prices[i]+dp[i-1,1,1])

#所能获取的最大利润
max(dp[n-1,1,0],dp[n-1,2,0])

详解动态规划算法(Python)_第25张图片

你可能感兴趣的:(算法,动态规划,算法,python)