動態規劃應用於子問題重疊的情況,即不同的子問題具有公共的子子問題。
動態規劃的重點在於用表存儲子子問題的解,使得每個子子問題只用求解一次,避免重複計算。(空間換時間)
動態規劃通常用來求解最優化問題
動態規劃兩個特徵
1.子問題重疊
2.最優子結構
四個步驟來設計一個動態規劃算法
1.刻畫一個最優解的結構特徵(尋找最優子結構,然後利用這種子結構從子問題的最優解構造出原問題的最優解)
2.遞歸的定義最優解的值
3.計算最優解的值,通常採用自底而上的方法
4.利用計算出的信息構造最優解
LeetCode的最長迴文子串問題就是用到了動態規劃
15.1鋼條切割問題
給定一段長度爲n英寸的鋼條和一個不同長度鋼條價目表pi(i=1,2,...,n)求切割方案
最優解是把可以迭代原鋼條切成兩段的最大值來做到,但是如果可以把一段鋼條切割最大值存起來,下次調用它的時候就不用再迭代了。時間複雜度就從O(2^n)變爲O(n^2)。
鋼條切割問題滿足最優子結構性質:問題最優解由相關子問題的最優解構成,而這些子問題可以單獨求解。
提到這點是因爲有時最優解不是由相關子問題的最優解構成。
以下是解決這個問題的代碼:允許設置鋼條切割費用,返回割切後各鋼條長度
package cutSteel;
import java.util.*;
public class cutSteel {
public static void main(String[] args) {
int steelLength = 15;//steelLength爲原鋼條長度
int cutPay = 3;//切割一次的費用
int[] price = {0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30};
int priceLength = price.length;
int[] maxIncome = new int[steelLength+1];
String[] cutPlan = new String[steelLength+1];// 以字符串形式存儲每次切割的長度
for (int i = 0; i <= steelLength; i++) {
if(i <= priceLength - 1) {//price數組範圍內的初始化爲price數組對應值
maxIncome[i] = price[i];
} else {
maxIncome[i] = -10000;//超出價目表範圍的,一定要切
}
cutPlan[i] = String.valueOf(i);// 初始化爲對應i
for(int k = 0; k <= i/2; k++) {
int Income = maxIncome[k] + maxIncome[i-k] - cutPay;
if (Income > maxIncome[i]) {// 要用大於號,否則字符串內可能混入多的0,導致分解切割長度錯誤
maxIncome[i] = Income;
cutPlan[i] = cutPlan[k] + "," + cutPlan[i-k];
}
}
}
System.out.println(maxIncome[steelLength]);
System.out.println(cutPlan[steelLength]);//也可以把string轉化爲char數組,再轉化爲數組輸出
}
}
15.2 矩陣鏈乘法
https://blog.csdn.net/qq_39980514/article/details/90209170
15.3 動態規劃原理
具有最優子結構:一個問題的最優解包含其子問題的最優解
具有重疊子問題:如果遞歸算法反覆求解相同的子問題。
通過存儲算出的子問題答案,來避免重複計算
算法有兩種
1.自頂向下的動態規劃算法(如果子問題空間中的某些子問題不必求解,則更快,因爲算的少)
2.自底而上的動態規劃算法(如果每個子問題都必須求解一次,則更快,因爲沒有遞歸調用的開銷)