先从最经典的sample:爬楼梯开始:
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 注意:给定 n 是一个正整数。 示例 1:
输入: 2 输出: 2 解释: 有两种方法可以爬到楼顶。 1. 1 阶 + 1 阶 2. 2 阶 复制代码 复制代码
示例 2:
输入: 3 输出: 3 解释: 有三种方法可以爬到楼顶。 1. 1 阶 + 1 阶 + 1 阶 2. 1 阶 + 2 阶 3. 2 阶 + 1 阶 复制代码
函数签名:
public static int climbStairs(int n) {}
复制代码
最简单的 – 暴力破解
dp最基本的就是推导出递归公式。
假设前面有n层台阶(n足够大),那么可以走1步或者2步,这这两个的选择是不同的,因此在当前层的走法是:
走一步 + 走两步
走一步和走两步的结果也可以分解成再继续往下的走一步和走两步。。。的若干组合。
那么对于当前层的结果,对下层的结果是不关心的,那么可以得出递归的公式:
F(n) = F(n-1) +F(n-2)
那么最后,只要加上终止条件,就可以组成一个解法。
容易得出终止条件:
- 前面没有台阶了,是一种走法
- 前面还有1个台阶,也是一种走法
即:
if(n <= 1) return 1;
那么最朴素的解法就有了:
public static int climbStairs(int n) {
if(n <= 1){
return 1;
}
return climbStairs(n-1)+climbStairs(n-2);
}
复制代码
在leetcode上提交的结果,就是超时的,思路是对的。
暴力破解2.0 – 每层留个记录
暴力破解超时了,那么在时间的复杂度上有没有什么可以优化的方法?
根据上面的解法,根据迭代的性质,可以很容易的就推导出最终的结果都是若干个F(0)+F(1)+F(2)+… 。
如果把在前面还剩n层时的结果计算之后存下来,下次用的时候就不用计算了,那就能节省一部分时间。
根据这个思路就有了以下的解法:
private static List<Integer> mo = new ArrayList<>();
public static int climbStairs(int n) {
if(mo.size()==0){
mo.add(1);mo.add(1);
}
if(n <= 2) return mo.get(n);
if(mo.size()>n) return mo.get(n);
int cur = climbStairs(n-1)+climbStairs(n-2);
mo.add(cur);
return cur;
}
复制代码
在这里我们通过一个不定长数组,对已知的记录进行保存。最终的结果就是:
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:35.2 MB, 在所有 Java 提交中击败了43.89%的用户
在dp中这个方法又称为备忘录。
暴力破解3.0 – 备忘录瘦身
使用数组以及局部变量数组进行进一步简化
通过n可以知道:
- 事实上数组的容量是确定的n+1(从0到n一共n+1个数),可以使用数组代替arrayList,节省扩容以及函数调用的时间
但通过这种进行处理的话就不能通过全局变量来记录备忘信息,要么通过新建一个方法,要么使用循环来代替迭代。
此处使用循环,也可以避免StackOverflow的问题。
- 循环思路:
- 容易得知:我们需要的仅仅是第n阶的结果,因此返回dp[n]即可。
public static int climbStairs(int n) {
int[] dp = new int[n+1];
dp[0] = 1;dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i-1]+dp[i-2];
}
return dp[n];
}
复制代码
使用滚动数组对备忘录进行瘦身
根据上面数组的方法,得到一个启示:
- 其实我们只关心F(n-1)和F(n-2)的值,所以我们用3个变量来代表F(n),F(n-1),F(n-2),在循环体中往下滚动,也能做到相同的效果。
- 这种方法称为 滚动数组。
public static int climbStairs(int n) {
int curRes = 1,ancestor = 1,prev = 1;
for (int i = 2; i <= n; i++) {
curRes = ancestor + prev;
ancestor = prev;
prev = curRes;
}
return curRes;
}
复制代码
到这里就得到通过DP对这道题最优的解法了。
总结:
- DP中最重要的就是一开始推导出的公式。
- 通过备忘录进行记录信息,如果条件允许,可以使用滚动数组替代数组做空间上的优化。