轉自:https://www.cnblogs.com/tgycoder/p/5037559.html 侵刪
免責聲明:轉發是因爲我覺得好使,你若是覺得看着不順眼,出門右滾不送。
動態規劃(dynamic programing)和分治法類似,都是通過組合子問題的解來求解原問題的解。(在經典排序算法中的二路歸併排序和快速排序都用到了分而治之的思想-分治法)。
分治法是將原問題劃分爲沒有交集,相互獨立的子問題,並分別求解後再進行合併,求出原問題的解。
動態規劃應用於子問題重疊的情況,即不同的子問題具有公共的子子問題。分治法會做許多不必要的工作,它會反覆地求解那些公共子問題。動態規劃算法對每個子問題只求解一次,將其解保存在一個表格中,從而無需每次求解一個子子問題時都需要重新計算。
動態規劃上用來求解最優化問題(optimization problem)。
可以按照下面四個步驟來設計一個動態規劃算法:
1、刻畫一個最優解的結構特徵。
2、遞歸地定義最優解的值。
3、計算最優解的值,通常採用自底向上的方法。
4、利用計算出的信息構造一個最優解。
最優子結構
用動態規劃求解最優化問題的第一步就是刻畫一個最優解的結構特徵。如果一個問題的最優解包含其子問題的最優解,我們稱此問題具有最優子結構性質。因此,某個問題是否適合用動態規劃,它是否具有最優子結構性質是一個好的標準。使用動態規劃方法時,我們用子問題的最優解來構造原問題的最優解。
如何發掘最優子結構的性質?
1、證明問題最優解的第一個組成部分是做出一個選擇,而做出這個選擇將會產生一個或多個待解的子問題。
2、對一個給定問題,在其可能的第一步選擇中,假定已經知道哪種選擇纔會得到最優解。而我們並不關心這種選擇具體是如何得到的,只是假定已經知道了這種選擇。
3、給定獲取的最優解選擇後,確定這次選擇會產生哪些子問題,以及如何最好地刻畫子問題空間。
4、利用“剪切-粘貼(cut and paste)”技術證明作爲構成原問題最優解組成部分,每個子問題的解就是它本身的最優解。
反證法:假定子問題的解不是自身的最優解,那麼我們就可以從原問題中剪切掉這些非最優解,將最優解粘貼進去,從而得到原問題一個更優的解,這個解與最初的解的前提假設矛盾。
刻畫子問題空間的經驗
保持子問題空間儘量簡單,只在必要時才擴展它。例如下一節的例子,求鋼條切割的最大收益問題中,子問題空間包含的問題爲:對每個i值,長度爲i的鋼條最優切割問題。
對於不同問題領域,最優子結構的不同體現在兩個方面:
- 原問題的最優解中涉及到多個子問題。
- 在確定最優解使用哪些子問題時,需要考察多少種選擇。
重疊子問題
適合用動態規劃方法求解最優化問題的第二個性質是子問題的空間必須足夠小,即問題的遞歸算法會反覆地求解相同的子問題,而不是一直生成新的子問題。動態規劃算法會對重疊的子問題只求解一次,並保存在一張表裏,需要用的時候直接查表即可,每次查表的時間代價爲常量O(1)。
寫代碼是一種藝術,甚於蒙娜麗莎的微笑。
62. Unique Paths
m行n列的數組,只能往下或者往右走,看走到最後一個元素有多少種走法
Example 1:
Input: m = 3, n = 2
Output: 3
Explanation:
From the top-left corner, there are a total of 3 ways to reach the bottom-right corner:
1. Right -> Right -> Down
2. Right -> Down -> Right
3. Down -> Right -> Right
Example 2:Input: m = 7, n = 3 Output: 28
/**
* 最後一列初始化爲1,然後dp[i][j]=dp[i][j+1] + dp[i+1][j]
* @param m
* @param n
* @return
*/
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
//初始化最後一列爲1
for(int i=0;i<m;i++){
dp[i][n-1]=1;
}
//初始化最後一行爲1
for(int j=0;j<n;j++){
dp[m-1][j]=1;
}
//從右向左 從下至上更新
for(int j=n-2; j>=0;j--){
for (int i=m-2;i>=0;i--){
//等於同一行+同一列相鄰的鄰居走法之和
dp[i][j] = dp[i][j+1] + dp[i+1][j];
}
}
return dp[0][0];
}
L120:Triangle
Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.
For example, given the following triangle
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).
public int minimumTotal(List<List<Integer>> triangle) {
int rows = triangle.size(); //行數
int cols = triangle.get(rows-1).size();//列數
int[][] dp = new int[rows][cols];
dp[0][0] = triangle.get(0).get(0);
for(int row=1;row<rows; row++){
List<Integer> curRow = triangle.get(row);//本行的所有shu
int col =0;
// dp[1][0]、dp[2][0]這種左邊界值
dp[row][col] = dp[row-1][col] + curRow.get(col);
col=1;
//遍歷找最大的
for(; col <curRow.size()-1;col++){
dp[row][col] = Math.min(dp[row-1][col-1],dp[row-1][col])+curRow.get(col);
}
//右邊界,上一行累計下來的最小值+右邊界
dp[row][col] = dp[row - 1][col - 1] + curRow.get(col);
}
//最後一行取值
int min = Integer.MAX_VALUE;
for(int col=0; col <cols; col++){
min = Math.min(min, dp[rows-1][col]);
}
return min;
}