我如今是初學的狀態,在此來記錄個人刷題過程,便於之後複習鞏固。java
我leetcode從動態規劃開始刷,語言用的java。數組
我上網查了一下動態規劃,瞭解到動態規劃是「帶有備忘錄的遞歸」,優化
而大多數用來理解動態規劃的例子都是斐波那契數列,就是那個經典的遞歸式spa
f(i)=f(i-1)+f(i-2) ,f(1)=f(2)=1code
blog
f(5)=f(4)+f(3);遞歸
f(4)=f(3)+f(2);索引
f(3)=f(2)+f(1);leetcode
而後咱們就發現了重複的部分,在求f(5)和f(4)的時候都要求f(3),那麼它們都要作一次f(3)的遞歸操做,來獲得f(3)的值。get
我想這是很不值得的,不必一樣的操做執行兩遍。而且咱們知道當f(n)的n比較大時,是不少重複的部分的,這也就意味着有很大的優化空間。
所以有了所謂的「備忘錄」,也就是用一個數組來記錄每一個狀態的結果,好比f(5)就是n爲5時f(n)的狀態。
這樣的話,咱們就能夠在求f(n)的時候,先查看一下數組中是否記錄了這一個狀態的值,若是有,就直接從數組中拿,若是沒有,就遞歸計算一下,再把這個值放到數組中去。這也是所謂的「以空間換時間」的思想。
int[] dp=new int[n+1];//dp[i]表示f(i)的值
在求f(x)時:
if(dp[x]==0)//未被記錄到數組
dp[x]=f(x-1)+f(x-2)
return dp[x];
同時,遞歸也是會花費不少時間的,咱們可否換一種方式呢?
這時候咱們發現f(n)的狀態之間存在遞推關係,也就是f(n)=f(n-1)+f(n-2)
那麼這就對應了動態規劃的第二個關鍵因素狀態轉移方程,咱們把遞推關係轉化成數組dp先後的關係,
好比斐波拉契數列的就是dp[i]=dp[i-1]+dp[i-2]
有了這個方程,咱們就能夠循環求dp[i]的值了
dp[5]=dp[4]+dp[3],
dp[4]=dp[3]+dp[2],
dp[3]=dp[2]+dp[1];
那麼在求dp[5]的時候dp[4]和dp[3]已是保存在數組了,即可以直接得到。
咱們知道遞推和遞歸同樣,須要出口,也就是遞歸或遞推到底的標誌
在這道題中出口就是dp[1]=dp[2]=1;
有了這兩個值,在循環的時候咱們就能夠求出全部的值了,這就是出口的意義。
感受能夠類比數學裏數學概括法。
//須要先作個判斷
if(n==1||n==2)
return 1;
dp[1]=dp[2]=1;
for(int i=3;i<n+1;i++)
{
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
最後,我以爲重要的就是把握總體的邊界狀況,好比這裏的n==1和n==2是不用遞推關係的,並且dp[1]=dp[2]=1以前須要肯定n>2才能賦值的,有的題目裏還有給出參數爲一個數組,這時須要考慮數組長度爲0的狀況等等
最後總結一下動態規劃的四個要素(本身總結的):
1.定義數組
2.找出遞推關係
3.找出出口
4.把握總體邊界
它們在程序中的位置是4->1->3->2
最後返回值
假設你正在爬樓梯。須要 n 階你才能到達樓頂。
每次你能夠爬 1 或 2 個臺階。你有多少種不一樣的方法能夠爬到樓頂呢?
注意:給定 n 是一個正整數。
示例 1:
輸入: 2
輸出: 2
解釋: 有兩種方法能夠爬到樓頂。
1 階 + 1 階
2 階
示例 2:
輸入: 3
輸出: 3
解釋: 有三種方法能夠爬到樓頂。
1 階 + 1 階 + 1 階
1 階 + 2 階
2 階 + 1 階
1.設置數組:
經過題目,咱們知道咱們最終要求的是到達n階有多少種方法,那不妨就設這個爲dp[n]
那咱們要求的數組dp[i]表示的就是到達i階時有dp[i]種方法
2.找出遞推關係:
那咱們就要了解dp[i]是怎麼來的了?
根據題目,咱們知道,每次是能夠跨一階或者兩階的,那麼dp[i]就只有兩種方法獲得
一種是由dp[i-2]跨兩階來的,還有一種是dp[i-1]跨一階來的。
那麼dp[i]的方法數應該等於dp[i-1]的加上dp[i-2]了。
所以,咱們找出了遞推關係dp[i]=dp[i-1]+dp[i-2]
3.找出出口:
根據遞推式咱們知道i>=2才能使用遞推獲得,否則下標就要<0了
那咱們求一下出口dp[0]=0,0階的時候確定只有0種方法
dp[1]=1;1階的時候只有跨1階這一種方法。
可是這裏還有一個dp[2]也是出口,可能被忽略掉,由於按照遞推式dp[2]=dp[1]+dp[0]=1,而實際上dp[2]=2
4.把握總體邊界:
n<=0,n==1,和n==2能夠提早算出
代碼
class Solution {
public int climbStairs(int n) {
//1.考慮總體邊界
if(n<=0)
return 0;
if(n==1||n==2)
return n;
//2.設置數組
int []dp=new int[n+1];//dp[i]表示到達i階,有dp[i]種方法
//3.考慮數組邊界值
dp[0]=0;
dp[1]=1;
dp[2]=2;//注意2也是邊界
//4.找出dp[i]與dp[i-1]的關係,循環獲取所要得到的項dp[n];
//dp[i]=dp[i-1]+dp[i-2]
//要到達n階能夠有兩種方法:一種是從i-1爬1階來的,還有一種是i-2爬2階來的
//所以須要求這兩種方法之和
for(int i=3;i<=n;i++)
{
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
}
數組的每一個索引作爲一個階梯,第 i個階梯對應着一個非負數的體力花費值 costi。
每當你爬上一個階梯你都要花費對應的體力花費值,而後你能夠選擇繼續爬一個階梯或者爬兩個階梯。
您須要找到達到樓層頂部的最低花費。在開始時,你能夠選擇從索引爲 0 或 1 的元素做爲初始階梯。
示例 1:
輸入: cost = [10, 15, 20]
輸出: 15
解釋: 最低花費是從cost[1]開始,而後走兩步便可到階梯頂,一共花費15。
示例 2:
輸入: cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
輸出: 6
解釋: 最低花費方式是從cost[0]開始,逐個通過那些1,跳過cost[3],一共花費6。
注意:
cost 的長度將會在 [2, 1000]。
每個 cost[i] 將會是一個Integer類型,範圍爲 [0, 999]
1.設置數組:
經過題目,咱們知道咱們最終要求的是到達n階的最低花費,那不妨就設這個爲dp[n]
那咱們要求的數組dp[i]表示的就是到達i階時的最低花費,根據示例,咱們可知最後要返回的結果應該是dp[len]
2.找出遞推關係:
首先咱們分析題目可知,消耗的體力值應該等於原來的加上到達的那一階的體力值,所以若是跨兩階的話,應該是直接加上兩階中的後一階的體力值的。
由於一次只能跨一階或者兩階,所以dp[i]應該是dp[i-2]和dp[i-1]中比較小的那個加上i對應的體力值,即cost[i];
所以dp[i]=Math.min(dp[i-2],dp[i-1])+cost[i]
3.找出出口:
根據遞推式下標咱們知道i>1才能使用遞推式
那麼就須要求出dp[0]和dp[1]
dp[0]=cost[0];//到達0階時的花費,只有一個
dp[1]=cost[1];//到達1階一種是一階一階上,即cost[0]+cost[1],還有一種是直接上兩階cost[1],cost[1]更小
4.把握總體邊界:
參數是一個cost數組,咱們須要考慮數組長度爲0的狀況,遞推式不覆蓋下標爲0和1的,所以也應該拿出來做爲出口
if(len==0)
return 0;
if(len==1)
return cost[0];
if(len==2)
return cost[1];
class Solution {
public int minCostClimbingStairs(int[] cost) {
//設置出口
int len=cost.length;
if(len==0)
return 0;
if(len==1)
return cost[0];
if(len==2)
return cost[1];
//設置數組
int []dp=new int[len];//dp[i]表示到達第i階時所花費的最小體力值
``
//設置數組邊界
dp[0]=cost[0];
dp[1]=cost[1];
//找出數組的遞推關係
int i;
for(i=2;i<len;i++)
{
dp[i]=Math.min(dp[i-2],dp[i-1])+cost[i];
}
//返回值
}
}