【leetcode】動態規劃案例彙總

背景, 基本思路,案例模板

背景

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)進行記錄;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章