動態規劃對於很多人來說是一道過不去的坎,因爲很多的教程或者書籍都講得太抽象,讀者看了都雲裏霧裏
其實動態規劃是很簡單的,今天,我就來講講動態規劃是怎麼實現的.
一 動態規劃作用:
動態規劃一般是來解決
1計數
2求最大值,最小值
3求存在性
二 動態規劃怎麼用(四部曲):
1.確定狀態(兩個核心:1最後一步 2化成子問題)
2轉移方程
3開始和邊界條件
4計算順序
這麼一說,太抽象了,這四部曲是什麼鬼東西(黑人問號.jpg)???接下來先用例子分析分析
問題:(lintcode:669)
首先,看到這種題,先不考慮算法,直接憑直覺的想法是這樣的
7元*3+5元*1+2元*1=28 (呃,好像不能這樣),再這樣
7元*3+2元*3=27 ,一共6枚硬幣(這樣總可以了吧)
其實,正確答案是7元*1+5元*4=27 ,5枚硬幣(震驚!!!!!!)
接下來用動態規劃來(四部曲)分析:
1.確定狀態
1.1"最後一步"
首先我們把最後一個硬幣的面值是ak,那麼前面的硬幣值肯定是27-ak
接下下,"最後一步"還有兩個注意的關鍵點
1.2 化解子問題
還有個問題,我們還不知ak是多少,所以需要
這樣我們的第一步就完成了
2 確定轉移方程
根據第一步的狀態,我們這樣設置,(注意,上面的大括號在這一步已經變成方括號了)
完成這一步可以先喝杯咖啡了
3.確定開始和邊界條件
首先我們的硬幣肯定從f[0]即湊出0元需要0枚硬幣,
接下來是邊界問題:
4計算順序
有了我們前面分析的前三步,接下來就是計算順序了,其實就是從f[0]開始計算而已,並把他們都記錄下來
結果其實是這樣的
小結:
說了這麼多,沒代碼啊,無代碼無真相!!接下來上代碼,
/**
*
* @param {Array} coin 代表硬幣的種類 [2,5,7]
* @param {int} M 代表拼湊的值 27
*/
function coinChange(coin, M) {
let f = []
f[0] = 0 //初始化第一步
for (let i = 1; i <= M; i++) {
f[i] = Number.MAX_VALUE //開始默認無窮大,即不能拼湊
for (let j = 0; j < coin.length; j++) { //這裏右三種面值的硬幣,需要循環一次
if (i - coin[j] >= 0 && f[i - coin[j]] != Number.MAX_VALUE) {//小於0即不能拼湊,和無窮大+1會報錯
f[i] = Math.min(f[i], f[i - coin[j]] + 1)
}
}
}
if (f[M] == Number.MAX_VALUE) {
return -1
}
return f[M] //數組最後一個值就是我們想要的結果
}
接下來,繼續看計數型的動態規劃(lintcode:114)
解答這個問題,還是四部曲:
1.確定狀態
1.1 "最後一步"
1.2化成子問題
2. 確定轉移方程
3.確定開始和邊界條件
4計算順序
分析完了,上代碼
/**
*
* @param {int} m m行
* @param {int} n n列
*/
/**
* @param m: positive integer (1 <= m <= 100)
* @param n: positive integer (1 <= n <= 100)
* @return: An integer
*/
const uniquePaths = function (m, n) {
let f=[];//先聲明一個一維數組的
for(let i=0;i<m;++i){
f[i]=[];//聲明二維數組的,其他語言有其他的寫法
for(let j=0;j<n;++j){
if(i===0||j===0){
f[i][j]=1;
}else{
f[i][j]=f[i-1][j]+f[i][j-1];
}
}
}
console.log(f)
return f[m-1][n-1];
};
接下來繼續分析一道題(lintcode:116):
1.確定狀態
1.1"最後一步"
1.2 化爲子問題
2. 確定轉移方程
3.確定開始和邊界條件
4. 計算順序
最後,上代碼
/**
*
* @param {Array} a //代表每個石頭能跳的長度
*/
function canJump(a){
let n=a.length //數組的長度爲能跳的總石頭數
let f=[]
f[0]=true
for(let j=1;j<n;j++){
f[j]=false
for(let i=0;i<j;i++){
if(f[i]&&a[i]+i>=j){
f[j]=true
break
}
}
}
return f[n-1]
}