很多人觉得动态规划很难,但它就是固定套路而已。其实动态规划只不过是将多余的步骤,提前放到dp数组中(就是一个数组,只不过大家都叫它dp),达到空间换时间的效果。它仅仅只是一种优化思路,因此它目前的境地和线性代数一样----虚假的难。
- 想想线性代数,在国外留学的学生大多数不觉得线性代数难理解。但是中国的学生学习线性代数时,完全摸不着头脑,一上来就是行列式和矩阵,根本不知道这玩意是干嘛的。
- 线性代数从根本上是在空间上研究向量,抽象上研究线性关系的学科。人家国外的教科书都是第一讲就帮助大家理解研究向量和线性关系。
- 反观国内的教材,直接把行列式搞到第一章。搞的国内的学生在学习线性代数的时候,只会觉得一知半解,觉得麻烦,完全不知道这玩意学来干什么。当苦尽甘来终于理解线性代数时干什么的时候,发现人家国外的教材第一节就把这玩意讲清楚了。你只会大骂我们国内这些教材,什么狗东西(以上是自己学完线性代数后的吐槽,我们同学无一例外都这么觉得)。
而我想告诉你,动态规划和线性代数一样,我学完了才知道,它不过就是研究空间换时间,提前将固定的重复操作规划到dp数组中,而不用暴力求解,从而让效率极大提升。
- 但是网上教动态规划的兄弟们,你直接给一个动态方程是怎么回事?和线性代数,一上来就教行列式和矩阵一样,纯属恶心人。我差不多做了30多道动态规划题目,才理解,动态方程只是一个步骤而已,而这已经浪费我很长时间了,我每道题都一知半解不理解,过程及其痛苦。最后只能重新做。
- 动态规划,一定是优先考虑重复操作与dp数组之间的关系,搞清楚后,再提出动态方程。而你们前面步骤省略了不讲,一上来给个方程,不是纯属扯淡吗?
- 我推荐研究动态规划题目,按5个步骤,从上到下依次来分析
- DP数组及下标含义
- 递推公式
- dp数组初始化
- 数组遍历顺序(双重循环及以上时,才考虑)
- dp数组打印,分析思路是否正确(相当于做完题,检查一下)
- cost数组是离开每层楼梯向上爬所需体力,没有记录顶层,因为已经顶层了,没有比它搞一层的了,自然没有向上爬需要的体力。而我们要的是,到达顶层,需要的最小体力。
- 初始可以站在0号或1号位置,所以到达0和1号位置所需体力为0
- 每次要么攀登1层,要么攀登两层
- 之后的每一层,若想到达,需要花费的体力为:
从上一个攀登的楼梯“x”离开所需体力(cost数组中的值)+ 到达“x”已经花费的体力
- 因为我们只需要最小的体力花费,所以只记录较小的值。
- 暴力求解的思想,就是回溯算法,枚举每一种情况,拿到最小值,显然会做大量无效运算。
- 但是如果我们预先将其存储到dp数组,就可以直接通过dp[x], 获取dp数组中指定位置x的体力花费,而不用枚举。典型的动态规划题目
- DP数组及下标含义
我们要求出的是
最小体力花费,那么dp数组中存储的
就是最小体力花费。要求出谁的
最小体力花费就是下标的含义。显然是攀登到每层台阶的,那么下标就是代表谁的
体力花费,也就是代表是哪一层楼梯的
体力花费。显然,只需要一个下标即可表示,故这道题的dp数组只需要一维数组
- 递推公式
- 由题意可知,初始可以从0或1层开始,所以攀登到0或1所需体力花费为0.F(0) = F(1) = 0.
- 之后每一层,都需要
判断它的前两层,到这一层的花费,谁更小
。花费(从下面这两层到当前层X的花费)为:从下面两层离开所需的花费(cost中的值,因为要从它离开,到这个X层)+ 到达这两层本身已经花费的体力(到达下面那两层也是需要体力的)。也就是 F(n-1)+cost[n-1] 和 F(n-2)+cost[n-2]为到达X的两种方案花费。 - 因此,可以得到,从第二层开始,递推公式为前两层到它所需花费,且只考虑最小的那个 : F(n) = min( F(n-1)+cost[n-1] , F(n-2)+cost[n-2] )
- dp数组初始化
- 数组遍历顺序(因为这个数列是一维的,只需要一重循环,无需考虑这个)
- 打印dp数组(自己生成dp数组后,将dp数组输出看看,是否和自己预想的一样。)
class Solution {
public int minCostClimbingStairs(int[] cost) {
int length = cost.length;
int dp[] = new int[length+1];
dp[0]=dp[1] = 0;
for(int i = 2;i<=length;i++){
dp[i] = Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
}
return dp[length];
}
}
学有余力的同学可以尝试这个方法,将空间复杂度变为常数级-----------------代码:时间复杂度O(n).空间复杂度O(1) |
---|
将dp数组优化掉,换成3个变量,滚动执行。将dp[0]换成prev。dp[1]换成curr,dp[2]换成next。也就是prev始终指向 i-2的位置。curr指向i-1. next指向i
class Solution {
public int minCostClimbingStairs(int[] cost) {
int length = cost.length;
int prev = 0,curr = 0;
for(int i = 2;i<=length;i++){
int next = Math.min(curr+cost[i-1],prev+cost[i-2]);
prev = curr;
curr = next;
}
return curr;
}
}