背景, 基本思路,案例模板
背景
1 動態規劃在算法中,可以說是佔半壁江山,其中重要的原因是,通過該算法,將直來直去的暴力搜索法,其計算量直接降低一個數量級,其能力是十分顯著的;
2 其利用的數學技術就是最優化原理,這一先進的效能工具;
3 本文梳理各種優秀博客及書籍中,對動態規劃模板的總結,相互映照,取長補短,形成模式化思維;然後再結合具體的案例,進行應用實踐,以期能夠養成解決這一類問題的思維;
一、基本思路
1.1 觸發條件
是否可用動態規劃方法進行處理,一般需要遵循如下幾種條件。
1.1.1 算法的樂趣定義
1 最優化原理
1)問題的最優子結構的性質
2)不管之前是否是最優決策,都必須保證從現在開始的決策是在之前決策基礎上的最優決策,那麼這樣的最優子結構就符合最優化原理;
2 無後向性
1)之前的決策隻影響當前階段的決策,當時對之後各階段的決策不產生影響;
1.2 實現步驟
1.2.1 五分鐘學算法
動態規劃問題,一般分爲四個步驟,分別是:
1 問題分解
2 狀態定義
3 轉移方程推導
4 算法實現
1.2.2 算法的樂趣
1 定義最優子問題
1)確定問題的優化目標及最優解判定;
2)對決策過程分階段;可按照時間空間順序或者問題演化狀態;
2 定義狀態
1)既是決策對象,也是決策的結果;
2)對起始狀態施加決策,狀態發生變化,得到決策的結果狀態;
3)狀態的定義是建立在子問題定義的基礎上,因此狀態必須滿足“無後向性”。必要時,可以增加狀態的維度,引入更多的約束條件,使得狀態定義滿足“無後向性”條件;(什麼案例?裝配線問題,揹包問題中的剩餘容量約束)
3 定義決策與狀態轉移方程
1)從n-1階段,到n階段的演化規律;
2)如果狀態選擇不合適,導致子問題之間沒有重疊,也就不存在狀態轉移關係,會退化爲類似分治法那樣的樸素遞歸搜索算法;
4 確定邊界條件
1)遞歸終結條件;
2)對於遞推關係直接實現的動態規劃方法,需要確定狀態轉移方程遞歸式的初始條件或者邊界條件;
1.2.3 mu
muyids
動態規劃包含三個重要概念:最優子結構,邊界,狀態轉移公式
1 最優子結構
2 邊界:邊界是最簡單的最優子結構,無需再簡化便可得到結果;如果一個問題沒有邊界,將無法得到有限的結果;
3 狀態轉移方程;
針對本段話,我認爲邊界這塊強調的好,此外,我認爲邊界有兩個維度的含義,一是廣義上的最優子結構是一類邊界,二是狹義上的每個元素的取值範圍,也可以看做是一種邊界;
而將兩者綜合在一起看,後者就是前者細分後的值;
1.3 案例模板
基於上述1.1和1.2 進行描述,上面有,下面再寫;
1.4 其他博文一針見血的話
1 動態規劃說起來很高大上,說白了是以空間換時間,將計算結果封存起來,避免重複計算;
二、案例彙總
2.1 二維空間接雨水
2.1.1 觸發條件
1 最優化原理
1)每一列的最大盛水量之和相加,即爲總的盛水量;
2 無後向性
1)當某一列盛水量確定之後,不會發生改變,並且對之後決策不會產生影響;
2.1.2 實現步驟
1 問題分解
1)子問題是哪一個?
a 每一列的盛水量,是一個子問題;
b 當前列左(右)側的最大高度;然後借用木桶原理計算,即會演化爲子問題a
2)最優解
所有子問題的和,就是全局最優;
2 狀態定義
1)當前列左(右)側最大高度--maxLeft, maxRight;
2)當前列的高度--height[i]
3)當前列的儲水量;
3 轉移方程推導
1)maxLeft[i] = max(maxLeft[i-1], height[i-1])
4 邊界條件
1)maxLeft/maxRight的i邊界是[0, length - 1];
2) 初始條件:maxLeft[0] = 0, maxRight[length - 1] = 0; 因爲左右兩側不存在儲水的情況;
2.1.3 代碼
見題目代碼
#define fmax(a, b) (((a) > (b)) ? (a) : (b))
#define fmin(a, b) (((a) > (b)) ? (b) : (a))
int trap(int* height, int heightSize){
// 基於列進行查找處理;
// 1 求解每列的左右兩側的最大值;
// 2 針對每一個位置的值,基於木桶原理,進行求解;
int i, totalNum, min;
int *maxLeft = malloc(heightSize * sizeof(int));
int *maxRight = malloc(heightSize * sizeof(int));
totalNum = 0;
for (i = 1; i < heightSize - 1; i++) {
maxLeft[i] = fmax(maxLeft[i - 1], height[i - 1]);
}
for (i = heightSize - 2; i > 0; i--) {
maxRight[i] = fmax(maxRight[i + 1], height[i + 1]);
}
for (i = 1; i < heightSize - 1; i++) {
min = fmin(maxLeft[i], maxRight[i]);
if (min > height[i]) {
totalNum = totalNum + min - height[i];
}
}
free(maxLeft);
maxLeft = NULL;
free(maxRight);
maxRight = NULL;
return totalNum;
}
2.2 最長迴文字符串
2.2.1 觸發條件
1 最優化原理
1)結合中心擴展法,每一個字母爲中心的最長迴文字符中,選擇其中最大的;
2 無後向性
1)當前字符爲中心的求解,對其他字符沒有影響
2.2.2 實現步驟
1 問題分解
1)求解每個字符的最長迴文字符;
2)每個字符的求解方式:當前字符爲中心,向外擴展;
2 狀態定義
1)中心點的左邊座標;--left
2)右邊座標;--right
3)當前字符位置的最長迴文長度--length
4)中心點--i;
5)當前求出的最長迴文字符,初始值是1--maxLen;
3 狀態轉移方程
1)dp[left, right] = dp[left + 1, right - 1] 與 s[left] ?= s[right];
2)注意中心點起始位置,存在與中心點相同的情況
if (s.charAt(l) == s.charAt(r) && (r - l <= 2 || dp[l + 1][r - 1]))
3)狀態轉移方程爲當前階段,然後for循環驅動變化;
2.2.3 代碼
public String longestPalindrome(String s) {
if (s == null || s.length() < 2) {
return s;
}
int strLen = s.length();
int maxStart = 0; //最長迴文串的起點
int maxEnd = 0; //最長迴文串的終點
int maxLen = 1; //最長迴文串的長度
boolean[][] dp = new boolean[strLen][strLen];
for (int r = 1; r < strLen; r++) {
for (int l = 0; l < r; l++) {
if (s.charAt(l) == s.charAt(r) && (r - l <= 2 || dp[l + 1][r - 1])) {
dp[l][r] = true;
if (r - l + 1 > maxLen) {
maxLen = r - l + 1;
maxStart = l;
maxEnd = r;
}
}
}
}
return s.substring(maxStart, maxEnd + 1);
}
作者:reedfan
鏈接:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-fa-he-dong-tai-gui-hua-by-reedfa/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
2.3 跳石板
2.3.1 觸發條件
1 最優化原理
1)從前往後推導,每一步的最優,達到終點的最優;
2 無後向性
1)達到當前步驟是最優後,那麼從當前步開始的之後,都取決於之後的決定,而與之前的步驟無關;
2.3.2 實現步驟
1 問題分解
1)求解到達每一步的最小步數;
2)然後以此類推,找出到達最後終點的步數;
2 狀態定義
1)當前的石板位置--i;
2)到達當前石板的最小步數--dp[i];
3)下一步能夠走的步數--k;
4)存儲所能到達的下一個節點的最小步數--dp[i + k];
3 狀態轉移方程推導
1)dp[i + k] = min(dp[i + k], dp[i] + 1);
4 邊界條件
0)step = m - n + 1;
1)i屬於[0, step - 1]
2.4 把數字翻譯成字符串
2.4.1 觸發條件
2.4.2 實現步驟
1 問題分解
1)子問題爲求出【1-n】中的k位翻譯數字的最優,然後不斷拼接直到獲取n位字符串的最優;
2 狀態定義
1)當前字符長度--i--[0, count - 1]
2)當前位置的最大長度--dp[i]
3 狀態轉移方程
分爲兩種情況,在‘a’-'z'之間,則說明可以使用字母表示兩位數,否則,要麼不能跳轉,或者高位數是‘0’,本質上仍舊爲一位數;
1)dp[i] = dp[i-1];
2)dp[i] = dp[i-1] + dp[i-2];
4 邊界條件及實現
1)dp[0] = 1;
2)dp[1]的求解,需要單獨拎出來,因爲它不適用於dp[i - 2];
3)其他取值範圍,在狀態定義時已經寫過;
2.4.3 其他
1 我在本題花費時間比較長的非智力因素是:起始位置爲零或者爲一,沒有規劃好;
2 關於數字轉化爲字符數組的兩種方法,位於csdn的另一篇文章的[2.2小節](https://blog.csdn.net/qq_31382031/article/details/105344489)進行記錄;