基於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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章