定義動態規劃的步驟
例題分析
演算法對比
總結動態規劃演算法(dynamic programming-dp)是通過拆分問題,定義問題狀態和狀態之間的關係,將待求解的問題分解為若干個子問題(階段),按順序求解子階段,前一子問題的解,為後一子問題的求解提供了有用的資訊。在求解任一子問題時,列出各種可能的區域性解,通過決策選取那些有可能達到最優的區域性解。依次解決各子問題,最後乙個子問題就是初始問題的解。
問題具有最優子結構性質。如果問題的最優解所包含的子問題的解也是最優的,我們就稱該問題具有最優子結構性質。
無後效性:某狀態以後的過程不會影響以前的狀態,只與當前狀態有關。
有重疊子問題:即子問題之間是不獨立的,乙個子問題在下一階段決策中可能被多次使用到。(該性質並不是動態規劃適用的必要條件,但是如果沒有這條性質,動態規劃演算法同其他演算法相比就不具備優勢)
已知問題規模為n的前提a,求解乙個未知解b。(我們用an表示"問題規模為n的已知條件")
此時,如果把問題規模降到0,即已知a0,可以得到a0->b.
1)如果從a0新增乙個元素,得到a1的變化過程。即a0->a1; 進而有a1->a2; a2->a3; …… ; ai->ai+1. 這就是嚴格的歸納推理,也就是我們經常使用的數學歸納法;
對於ai+1,只需要它的上乙個狀態ai即可完成整個推理過程(而不需要更前序的狀態)。我們將這一模型稱為馬爾科夫模型。對應的推理過程叫做"貪心法"。
2)然而,ai與ai+1往往不是互為充要條件,隨著i的增加,有價值的前提資訊越來越少,我們無法僅僅通過上乙個狀態得到下乙個狀態,因此可以採用如下方案:
; ; ;……; ->ai+1. 這種方式就是第二數學歸納法。
對於ai+1需要前面的所有前序狀態(或幾個狀態)才能完成推理過程。我們將這一模型稱為高階馬爾科夫模型。對應的推理過程叫做"動態規劃法"。
這個步驟可能有點枯燥,建議結合後面例題進行閱讀。
子問題是和原問題相似,但規模較小的問題。第一步就是縮小規模,利用小規模子問題刻畫其結構特徵。
劃分階段:按照問題的時間或空間特徵,把問題分為若干個階段。在劃分階段時,注意劃分後的階段一定要是有序的或者是可排序的,否則問題就無法求解。
確定狀態和狀態變數:將問題發展到各個階段時所處於的各種客觀情況用不同的狀態表示出來。當然,狀態的選擇要滿足無後效性。
說明:建立乙個一維陣列或者二維陣列,儲存每乙個子問題的結果,具體建立一維陣列還是二維陣列看題目而定
注意:子問題的解一旦求出就會被儲存,所以每個子問題只需求 解一次。
找出狀態轉換方程,也就是說找到每個狀態跟他上乙個狀態的關係,根據狀態轉化方程寫出**。
確定一些初始狀態(邊界狀態)的值
確定狀態轉移方程: 定義出什麼是"狀態",以及如何從乙個或多個"值"已知的 "狀態",求出另乙個"狀態"的"值"(遞推型)。狀態的遷移可以用遞推公式表示,此遞推公式也可被稱作"狀態轉移方程"。
在確定了子問題的遞推關係之後,下一步就是依次計算出這些子問題了。一般地,動態規劃有兩種計算順序:
自頂向下的、使用備忘錄的遞迴方法
自底向上的、使用 dp 陣列的迴圈方法。
不過在普通的動態規劃題目中,99% 的情況我們都不需要用到備忘錄方法,即自底向上的 dp 陣列。
目的:空間複雜度的較小。
在很多問題中,dp陣列並非全部用上,很多情況下只使用一小部分或空間可重複利用,這給空間壓縮帶來可能。
你是乙個專業的小偷,計畫偷竊沿街的房屋。每間房內都藏有一定的現金,影響你偷竊的唯一制約因素就是相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警。
給定乙個代表每個房屋存放金額的非負整數陣列,計算你在不觸動警報裝置的情況下,能夠偷竊到的最高金額。
示例 1:
輸入: [1,2,3,1]
輸出: 4
解釋: 偷竊 1 號房屋 (金額 = 1) ,然後偷竊 3 號房屋 (金額 = 3)。
偷竊到的最高金額 = 1 + 3 = 4 。
示例 2:
輸入: [2,7,9,3,1]
輸出: 12
解釋: 偷竊 1 號房屋 (金額 = 2), 偷竊 3 號房屋 (金額 = 9),接著偷竊 5 號房屋 (金額 = 1)。偷竊到的最高金額 = 2 + 9 + 1 = 12 。
每個房子的金額用h表示,那麼k個房子有兩種偷發,如上圖所示。容易得出遞推公式(重要):
注意這裡涉及到邊界值:
那麼,既然 dp 陣列中的依賴關係都是向右指的,dp 陣列的計算順序就是從左向右。這樣我們可以保證,計算乙個子問題的時候,它所依賴的那些子問題已經計算出來了。
確定了 dp 陣列的計算順序之後,我們就可以寫出題解**了:見3.3的**一。
最後一步計算 f(n) 的時候,實際上只用到了 f(n-1)和 f(n-2) 的結果。那麼只用兩個變數儲存兩個子問題的結果,就可以依次計算出所有的子問題。下面的圖比較了空間優化前和優化後的對比關係,**見3.3**二:
**一:未進行優化的dp
def rob(self, nums: list[int]) ->int:**二:優化後的dpif len(nums) ==0:
return
0
#子問題:
#f(k) = 偷 [0..k) 房間中的最大金額
#f(0) = 0
#f(1) = nums[0]
#f(k) = max
n =len(nums)
dp = [0] * (n+1)
dp[0] =0
dp[1] =nums[0]
for k in range(2, n+1):
dp[k] = max(dp[k-1], nums[k-1] + dp[k-2])
return dp[n]
def rob(self, nums: list[int]) ->int:動態規劃演算法:它通常用於求解具有某種最優性質的問題。在這類問題中,可能會有許多可行解。每乙個解都對應於乙個值,我們希望找到具有最優值的解。動態規劃演算法與分治法類似,其基本思想也是將待求解問題分解成若干個子問題,先求解子問題,然後從這些子問題的解得到原問題的解。與分治法不同的是,適合於用動態規劃求解的問題,經分解得到子問題往往不是互相獨立的。prev =0
curr =0
#每次迴圈,計算「偷到當前房子為止的最大金額」
for i in
nums:
#迴圈開始時,curr 表示 dp[k-1],prev 表示 dp[k-2]
#dp[k] = max
prev, curr = curr, max(curr, prev +i)
#迴圈結束時,curr 表示 dp[k],prev 表示 dp[k-1]
return curr
分治法:若用分治法來解這類問題,則分解得到的子問題數目太多,有些子問題被重複計算了很多次。如果我們能夠儲存已解決的子問題的答案,而在需要時再找出已求得的答案,這樣就可以避免大量的重複計算,節省時間。我們可以用乙個表來記錄所有已解的子問題的答案。
分治演算法不強調記錄算過的資料,動態規劃為了避免重複計算,一定會記錄資料。
不管該子問題以後是否被用到,只要它被計算過,就將其結果填入表中。這就是動態規劃法的基本思路。
【1】 六大演算法之三:動態規劃
【2】leetcode動態規劃的解析
動態規劃演算法(DP)
動態規劃演算法採用分治演算法的思想,將原問題分成若干個子問題,然後分別求解各個子問題,最後將子問題的解組合起來得到原問題的解。分治演算法遞迴地求解各個子問題,可能重複求解某些子問題。與分治演算法不同的是,動態規劃演算法不是遞迴地求解各個子問題,它是從簡單問題的解入手,逐步求解,直至求解出原問題。動態...
動態規劃演算法
一 動態規劃演算法原理 將待求解的問題分解成若干個相互聯絡的子問題,先求解子問題,然後從這些子問題的解得到原問題的解 對於重複出現的子問題,只在第一次遇到的時候對它進行求解,並把答案儲存起來。了不去求解相同的子問題,引入乙個陣列,把所有子問題的解存於該陣列中,這就是動態規劃所採用的基本方法。動態規劃...
動態規劃演算法
動態規劃 通過把原問題分解為相對簡單的子問題來求解複雜問題。動態規劃常常適用於有重疊子問題和最優子結構性質的問題。演算法總體思想 演算法的基本步驟 演算法的基本要素 最優子結構 重疊子問題 備忘錄方法 問題描述 子串行 公共子串行 最長公共子串行 lcs 問題 問題分析 動態規劃求解lcs問題 最長...