狀態先嘗試「題目問什麼,就把什麼設定為狀態」。然後緊接著考慮「狀態該如何轉移?」,如果「狀態轉移方程」不容易得到,就嘗試修改其定義,但目的仍然是為了方便得到「狀態轉移方程」。
2、思考狀態轉移方程(核心、難點)
狀態轉移方程是非常重要的,是動態規劃的核心,也是難點,起到承上啟下的作用。
技巧是分類討論。對狀態空間進行分類,思考最優子結構到底是什麼。即大問題的最優解如何由小問題的最優解得到。歸納「狀態轉移方程」是乙個很靈活的事情,得具體問題具體分析,除了掌握經典的動態規劃問題以外,還需要多做題。如果是針對面試,請自行把握難度,我個人覺得掌握常見問題的動態規劃解法,明白動態規劃的本質就是打**,從乙個小規模問題出發,逐步得到大問題的解,並記錄過程。動態規劃依然是「空間換時間」思想的體現。
3、思考初始化
初始化是非常重要的,一步錯,步步錯,初始化狀態一定要設定對,才可能得到正確的結果。
角度 1:直接從狀態的語義出發;4、思考輸出角度 2:如果狀態的語義不好思考,就考慮「狀態轉移方程」的邊界需要什麼樣初始化的條件;
角度 3:從「狀態轉移方程」方程的下標看是否需要多設定一行、一列表示「哨兵」,這樣可以避免一些邊界的討論,使得**變得比較短。
有些時候是最後乙個狀態,有些時候可能會綜合所有計算過的狀態。
5、思考狀態壓縮
「狀態壓縮」會使得**難於理解,初學的時候可以不一步到位。先把**寫正確,然後再思考狀態壓縮。
狀態壓縮在有一種情況下是很有必要的,那就是狀態空間非常龐大的時候(處理海量資料),此時空間不夠用,就必須狀態壓縮。
這道題比較煩人的是判斷回文子串。因此需要一種能夠快速判斷原字串的所有子串是否是回文子串的方法,於是想到了「動態規劃」。
「動態規劃」最關鍵的步驟是想清楚「狀態如何轉移」,事實上,「回文」是天然具有「狀態轉移」性質的:
乙個回文去掉兩頭以後,剩下的部分依然是回文(這裡暫不討論邊界)。依然從回文串的定義展開討論:
1、如果乙個字串的頭尾兩個字元都不相等,那麼這個字串一定不是回文串;
2、如果乙個字串的頭尾兩個字元相等,才有必要繼續判斷下去。
(1)如果裡面的子串是回文,整體就是回文串;
(2)如果裡面的子串不是回文串,整體就不是回文串。
即在頭尾字元相等的情況下,裡面子串的回文性質據定了整個子串的回文性質,這就是狀態轉移。因此可以把「狀態」定義為原字串的乙個子串是否為回文子串。
第 1 步:定義狀態
dp[i][j]
表示子串s[i, j]
是否為回文子串。
第 2 步:思考狀態轉移方程
這一步在做分類討論(根據頭尾字元是否相等),根據上面的分析得到:
dp[i]
[j]=
(s[i]
== s[j]
)and dp[i +1]
[j -
1]
分析這個狀態轉移方程:
(1)「動態規劃」事實上是在填一張二維**,i
和j
的關係是i <= j
,因此,只需要填這張表的上半部分;
(2)看到dp[i + 1][j - 1]
就得考慮邊界情況。
邊界條件是:表示式[i + 1, j - 1]
不構成區間,即長度嚴格小於2
,即j - 1 - (i + 1) + 1 < 2
,整理得j - i < 3
。
這個結論很顯然:當子串s[i, j]
的長度等於2
或者等於3
的時候,我其實只需要判斷一下頭尾兩個字元是否相等就可以直接下結論了。
如果子串s[i + 1, j - 1]
只有 1 個字元,即去掉兩頭,剩下中間部分只有 11 個字元,當然是回文;
如果子串s[i + 1, j - 1]
為空串,那麼子串s[i, j]
一定是回文子串。
因此,在s[i] == s[j]
成立和j - i < 3
的前提下,直接可以下結論,dp[i][j] = true
,否則才執行狀態轉移。
第 3 步:考慮初始化
初始化的時候,單個字元一定是回文串,因此把對角線先初始化為1
,即dp[i][i] = 1
。
事實上,初始化的部分都可以省去。因為只有乙個字元的時候一定是回文,dp[i][i]
根本不會被其它狀態值所參考。
第 4 步:考慮輸出
只要一得到dp[i][j] = true
,就記錄子串的長度和起始位置,沒有必要擷取,因為擷取字串也要消耗效能,記錄此時的回文子串的「起始位置」和「回文長度」即可。
第 5 步:考慮狀態是否可以壓縮
因為在填表的過程中,只參考了左下方的數值。事實上可以壓縮,但會增加一些判斷語句,增加**編寫和理解的難度,丟失可讀性。在這裡不做狀態壓縮。
下面是編碼的時候要注意的事項:總是先得到小子串的回文判定,然後大子串才能參考小子串的判斷結果。
思路是:
1、在子串右邊界 j 逐漸擴大的過程中,列舉左邊界可能出現的位置;
2、左邊界列舉的時候可以從小到大,也可以從大到小。
希望大家能夠自己動手,畫一下**,相信會對「動態規劃」作為一種「**法」有乙個更好的理解。
class
solution
:def
longestpalindrome
(self, s:
str)
->
str:
size =
len(s)
if size <2:
return s
dp =[[
false
for _ in
range
(size)
]for _ in
range
(size)
] max_len =
1 start =
0for i in
range
(size)
: dp[i]
[i]=
true
for j in
range(1
, size)
:for i in
range(0
, j)
:if s[i]
== s[j]
:if j - i <3:
dp[i]
[j]=
true
else
: dp[i]
[j]= dp[i +1]
[j -1]
else
: dp[i]
[j]=
false
if dp[i]
[j]:
cur_len = j - i +
1if cur_len > max_len:
max_len = cur_len
start = i
return s[start:start + max_len]
複雜度分析:
時間複雜度:o(n 2 )。
空間複雜度:o(n 2 ),二維 dp 問題,乙個狀態得用二維有序數對表示,因此空間複雜度是o(n 2)。
本文參考此處。
動態規劃 什麼是動態規劃?
先來看看 資訊學奧賽一本通第5版 是怎麼說的 動態規劃程式設計是對解最優化問題的一種途徑 一種方法,而不是一種特殊演算法。不像前面所述的那些搜尋或數值計算那樣,具有乙個標準的數學表示式和明確清晰的解題方法。動態規劃程式設計往往是針對一種最優化問題,由於各種問題的性質不同,確定最優解的條件也互不相同,...
mysql動態規劃 動態規劃
動態規劃 能夠動態規劃的問題具有以下特點 可分解成規模更小的子問題 子問題的結果可復用 關鍵是要理解狀態轉移方程的含義就好啦!數字三角形 問題描述 在數字三角形尋找從頂到底的路徑,使得路徑經過的數字之和最大。規定每一步只能往左下或右下走,求出最大路徑和。遞迴解法 include include us...
《動態規劃》 ACM 動態規劃例題詳解
描述 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 圖1 圖1給出了乙個數字三角形。從三角形的頂部到底部有很多條不同的路徑。對於每條路徑,把路徑上面的數加起來可以得到乙個和,你的任務就是找到最大的和。注意 路徑上的每一步只能從乙個數走到下一層上和它最近的左邊的那個數或者右邊的那個數。輸...