dp[001] 爬楼梯

先从最经典的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中最重要的就是一开始推导出的公式。
  • 通过备忘录进行记录信息,如果条件允许,可以使用滚动数组替代数组做空间上的优化。
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享