常見動態規劃的解決思路

字符串輸入的通常解決思路

  • 選擇suffix做爲子問題
  • 選擇prefix做爲子問題
  • 使用子集substring
  • 有時候單個的選擇已經不夠了,好比揹包問題,不只須要知道要選擇哪一個物件,來獲得價值,同時也須要知道還剩多少容量,也就是須要"記住"更多的子問題,或者說子條件來處理問題

編輯距離

給定兩個字串x和y,將x變成y所須要改變的字符的個數是多少?期間能夠的操做是:bash

  • 插入字符c 耗時O(1)
  • 刪除字符c 耗時O(1)
  • 將c更新爲c' 若是C和C'是相同的,耗時O(0),其它不考慮

字串能夠是非連續的。它等效於找兩個字符串的最大公共字串,好比 HIEROGLYPHOLOGY 和 MICHAELANGELO 最大公共字串爲 HELLOspa

動態規劃思路

  1. 子問題:X的子串X[i:]和Y的子串Y[j:]

數量爲:O(|X||Y|)指針

  1. 猜想:只有3中可能的方式,要麼把X[i]替換成Y[i],要麼刪掉X[i],要麼把Y[i]插入X
  2. 循環:DP(i,j)=min(cost of replace X[i] to Y[j]+ DP(i+1,j+1),cost of delete X[i]+DP(i+1,j),cost of insert Y[j]+DP(i,j+1) )

子問題的耗時:O(1)code

  1. 拓撲排序:三個方向由右下角向左下角開始執行
for i in 0,...,|x|:
    for j in 0,...,|y|:
複製代碼

原始的問題:DP(0,0),總的時間爲:O(1)*O(|x||y|)cdn

矩陣相乘在哪一個部分加括號運算才能使得運算最優

假設有以下形式的矩陣作乘法 blog

若是直接按照順序來計算,先乘A.B,獲得的結果再乘C

若是優先運算 B.C ,結果再乘A

能夠看到第二種方式消耗的時間會更少。
擴展到假設有n個矩陣相乘 A_0,...,A_{n-1}那麼如何實現經過加括號的方式來最優執行效率呢?

思路

最終總會是兩個矩陣相乘,那這兩個矩陣是怎麼計算獲得的?假設有一次區分,那麼它是(A_0,...,A_{k-1}).(A_k,...,A_{n-1})這種樣子,左邊括號中也會繼續按照這種方式劃分,同理右邊也須要,當左右兩邊都須要相似計算的時候,這種時候一般就是取substring排序

動態規劃解決思路

  1. 找到子問題:計算A_i,...,A_{j-1}

子問題的個數爲 n^2。每次選擇至關於1分爲2字符串

  1. 猜想:最後該執行那個部分的計算?

執行應該是(A_i,...,A_{k-1}).(A_k,...,A_{j-1})這種樣子 有的選擇的數量:O(n)。去嘗試全部的可能string

  1. 循環:(D(i,k)+D(k+1,j)+costof((A_i,...,A_{k-1}).(A_k,...,A_{j-1})) for \space k \space in \space range(i+1,j)

須要分別計算左邊括號的部分和右邊括號的部分,以及最後計算出來的左右兩個部分的乘積it

DP(i,i+1)=0

子問題的時間:O(n)。D(i,k)的計算忽略不計,只有k的選擇存在循環

  1. 拓撲執行順序:增長子問題的大小

總時間消耗:子問題個數 * 單個子問題的時間 = O(n^3)

  1. 原始的問題爲:DP(0,n)

如何使得詞在段落中的位置分配合理,使得更美觀

給定一個詞的集合words,使用badness(i,j)表示使用的單詞是words[i,j]

badness(i,j)=\lbrace^{\infty \space 頁面寬度小於所選文字的長度}_{(pageWidth-wordsLength)^3 \space 其它}

目標就是使得分出來的詞展現在各個行,使得\sum badness最小

很差看的展現以下

blah blah blah  
b    l    a   h  
verylongworld
複製代碼

但願它能展現成

blah    blah 
blah    blah  
verylongworld
複製代碼

暴力解決方案

一個個的去嘗試全部單詞的劃分,也就是說,去判斷任意一個詞,是否應該在它的位置換行,若是有n個單詞的話,總共須要嘗試的次數是 2^n

動態規劃

按照標準的動態規劃步驟來進行:

  1. 找到子問題:集合的後綴 words[i:]

假設找到了第一行的分隔點,那麼接下來須要考慮的是第二行又該在哪兒開始換行呢?依次繼續往下去查找,因此須要思考的子問題就是去掉第一行的詞以後,剩下的那些單詞

子問題的數量:n。只有n個單詞,後綴的次數也就是這些

  1. 猜想:第二行從哪兒開始?

猜想的選擇的數量:<=n-i=O(n)。每次選完了第一行,只須要在剩下的單詞裏面選

  1. 循環: DP[i]=min(badness(i,j)+DP[j] for j in range(i+1,n+1))

定義問題爲求DP(i)的最小值。當i=n的時候,是沒有單詞剩下的,花銷是0。
假設第一次在第i個位置開始換行,第一行的計算髮方式爲 badness(i,j),剩下的須要解決的問題部分是從i+1開始的單詞,也就是剩下部分的花銷假設從j開始,它可能取得剩下部分的任意值,每一個j的取值所須要的花銷就是D(j),那麼這兩部分相加最小時,也就是最優的劃分方式

循環部分耗時(每一個子問題耗時):O(n)。j總共有n種選擇,加法部分是常量

  1. 拓撲排序:i=n,n-1,...,0

須要先從最末端開始計算,再一層層的網上去累加 時間爲:問題的數量*每一個問題的花銷=n*O(n)=O(n^2)

  1. 檢驗原始問題是否解決:即DP(0)是否解決

使用一個指針parent來代表j中的最小值是那個,那麼沿着parent指針,0->parent[0]->parent[parent[0]]一直到最後,便可獲得最佳的劃分方式

吉他放手指的問題

有一個吉他,在彈的時候,能夠用任何一個手指來談,那麼若是給予一系列的音符notes,每一個音符都須要用手(手指頭取值 1,..,F)處理,每一個手指去某個音符彈後須要移到另外一個音符用一個手指去彈,假設描述這種移動使用d(p,f,q,g)表示花銷,那如何去使得花銷最小?

d(p,f,q,g):表示手指f移動到g,彈的音符從p到q

通常思考是:

  1. 子問題:notes[i:],即後面的音符怎麼去彈
  2. 猜想:該用那個手指頭來談音符i
  3. 循環:能夠選擇任意一個手指頭去彈新的音符,可是當從舊的音符切換到新的音符去彈的時候,沒法知道該切換到那個手指: DP(i)=min(DP(i+1)+d(i,f,i+1,?) for f in 1,..F)
    因此子問題"記住"的過少,須要增長考慮的狀況。

正確思路爲:

  1. 子問題:notes[i:],即後面的音符怎麼去彈,同時該那個手指f去彈notes[i:]
  2. 猜想:使用手指g來談notes[i+1]
  3. 循環: DP(i,f)=min(DP(i+1,g)+d(i,f,i+1,g) for g in 1,..F)

i表示note[i]的音符

  1. 拓撲排序
for i in reversed(range(n)): //全部的音符
    for f in 1...F: //每一個手指頭都有可能來彈音符
    
複製代碼

最終去解決原始問題,DP(0,f),可是須要指定f,因此使用 min(DP(0,f) for f in 1,...,F),即初始的時候應該選擇那一個手指去談第一個音符

這種場景便是要考慮更多的狀況,來達到最優解

相關文章
相關標籤/搜索