學會三個問題:
揹包問題
最長公共子串
最長公共子串行 (力扣原題)
特別說明:
這道題比較煩人的是判斷回文子串。因此需要一種能夠快速判斷原字串的所有子串是否是回文子串的方法,於是想到了「動態規劃」。
「動態規劃」的乙個關鍵的步驟是想清楚「狀態如何轉移」。事實上,「回文」天然具有「狀態轉移」性質。
乙個回文去掉兩頭以後,剩下的部分依然是回文(這裡暫不討論邊界情況);
依然從回文串的定義展開討論:
如果乙個字串的頭尾兩個字元都不相等,那麼這個字串一定不是回文串;
如果乙個字串的頭尾兩個字元相等,才有必要繼續判斷下去。
如果裡面的子串是回文,整體就是回文串;
如果裡面的子串不是回文串,整體就不是回文串。
即:在頭尾字元相等的情況下,裡面子串的回文性質據定了整個子串的回文性質,這就是狀態轉移。因此可以把「狀態」定義為原字串的乙個子串是否為回文子串。
第 1 步:定義狀態
dp[i][j]
表示子串 s[i…j] 是否為回文子串,這裡子串 s[i…j] 定義為左閉右閉區間,可以取到 s[i] 和 s[j]。
第 2 步:思考狀態轉移方程
在這一步分類討論(根據頭尾字元是否相等),根據上面的分析得到:
dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]
說明:「動態規劃」事實上是在填一張二維**,由於構成子串,因此 i 和 j 的關係是 i <= j ,因此,只需要填這張**對角線以上的部分。
看到 dp[i + 1][j - 1] 就得考慮邊界情況。
邊界條件是:表示式 [i + 1, j - 1] 不構成區間,即長度嚴格小於 2,即 j - 1 - (i + 1) + 1 < 2 ,整理得j - i < 3
。
這個結論很顯然:j - i < 3 等價於 j - i + 1 < 4,即當子串 s[i..j] 的長度等於 2 或者等於 3 的時候
,其實只需要判斷一下頭尾兩個字元是否相等就可以直接下結論了。
1.如果子串 s[i + 1…j - 1] 只有 1 個字元,即去掉兩頭,剩下中間部分只有 1 個字元,顯然是回文;
2.如果子串 s[i + 1…j - 1] 為空串,那麼子串 s[i, j] 一定是回文子串。
eg.[1,2,3] ; [2,3] 滿足終止條件
因此,在 s[i] == s[j] 成立和 j - i < 3 的前提下,直接可以下結論,dp[i][j] = true,否則才執行狀態轉移。
第 3 步:考慮初始化
初始化的時候,單個字元一定是回文串,因此把對角線先初始化為 true,即 dp[i][i] = true 。
事實上,初始化的部分都可以省去。因為只有乙個字元的時候一定是回文,dp[i][i] 根本不會被其它狀態值所參考。
第 4 步:考慮輸出
只要一得到 dp[i][j] = true,就記錄子串的長度和起始位置,沒有必要擷取,這是因為擷取字串也要消耗效能,記錄此時的回文子串的起始位置和回文長度即可。
第 5 步:考慮優化空間
因為在填表的過程中,只參考了左下方的數值。事實上可以優化,但是增加了**編寫和理解的難度,丟失可讀和可解釋性。在這裡不優化空間。
大家能夠可以自己動手,畫一下**,相信會對「動態規劃」作為一種「**法」有乙個更好的理解。
演算法與資料結構 動態規劃
動態規劃 dp 的基本思想是 當前子問題的解可由上一子問題的解得出。動態規劃演算法通常基於由乙個遞推公式 狀態轉移方程 和若干個初始狀態 狀態 應用 1 lis longest increasing subsequence 求乙個陣列中的最長非降子串行的長度。子問題 我們可以考慮先求a 0 a 1 ...
演算法與資料結構 動態規劃
用遞迴求解問題時,反覆的巢狀會浪費記憶體。而且更重要的一點是,之前計算的結果無法有效儲存,下一次碰到同乙個問題時還需要再計算一次。例如遞迴求解 fibonacci 數列,假設求第 n 位 從 1 開始 的值,c 如下 include intfib int n return fib n 1 fib n...
資料結構與演算法練習 動態規劃
hz偶爾會拿些專業問題來忽悠那些非計算機專業的同學。今天測試組開完會後,他又發話了 在古老的一維模式識別中,常常需要計算連續子向量的最大和,當向量全為正數的時候,問題很好解決。但是,如果向量中包含負數,是否應該包含某個負數,並期望旁邊的正數會彌補它呢?例如 連續子向量的最大和為8 從第0個開始,到第...