每日一题:LeetCode 70.爬楼梯

假设你正在爬楼梯。需要 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 阶

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/climbing-stairs
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

一般看到这种题目,暴力解法也不容易想到,那么只能用数学归纳法去寻找最近重复子问题,然后将解法泛化。

比如:

两级台阶f(2) 有两种方案
1 阶 + 1 阶
2 阶

三级台阶f(3) 有三种方案
1 阶 + 1 阶 + 1 阶
1 阶 + 2 阶
2 阶 + 1 阶

四级台阶f(4) 有几种方案?

四阶台阶不好计算,那么可以怎么想呢?

上四级台阶的时候,有两种可能:先上一阶,先上两阶

如果是先上一阶,那么还有三阶台阶,三阶台阶怎么上?f(3)不是已经求出来了么?

如果是先上两阶,那么还有两阶台阶,两阶台阶怎么上?f(2)不是已经求出来了么?

那么最终的方案就是f(4) = 先上一阶台阶的解决方案 + 先上两阶台阶的解决方案

也就是f(4) = f(3) + f(2)

五级台阶f(5) 有几种方案?

运用归纳法,可以这么算,如图所示:

红色的1和2表示先上一阶或者是先上两阶,那么上五阶的解决方案就是上四阶台阶和上三阶台阶的方案的总和,也就是f(4) + f(3). 

那么就可以很清晰的得出f(6),f(7).....f(n)的解决方案。

那怎么将上面的思想写进代码里面呢?最简洁的方案是用递归思想即可,当然用while循环也是可以的。

先看方法的调用链,是从上往下一层一层去压栈的,直到满足终止条件n<=2

然后再看方法的返回路径,是一层一层往上返回并且相加的。

那么就可以得出:

public int climbStairs(int n) {
      if (n <= 2) {
           return n;
      }
       
      return climbStairs(n - 1) + climbStairs(n - 2);
}

但是看整个递归树其实有很多的重复解,那么可以用HashMap把这些已经计算过的解缓存起来,过滤掉这些重复解,每种解决方案我只计算一次。

简洁后的代码:

class Solution {

    HashMap<Integer,Integer> map = new HashMap();

    public int climbStairs(int n) {
        if(n <= 2)return n;

        if(map.containsKey(n)){
            return map.get(n);
        }

        map.put(n,climbStairs(n - 1) + climbStairs(n - 2));

        return map.get(n);
    }
}

其实这是一种自上往下的方案,那么还有一种自下往上的方案,也就是动态规划

动态规划是求最优子结构的问题,如果是求递归树的最短路径倒是挺合适的,但是也是可以迁移到这道题目来。

求动态规划的几个步骤:

1 由于需要用到dp[ ]数组,那么我要先确定dp[i] 代表的含义是什么?

如何确定呢?从最后一步来看,将整个问题分解成子问题来求解。具体的来说,如果有5级台阶,那么第五级台阶的方案是怎么求的?也就是先退一步,或者是先退两步,把这两种方案的和加起来就好,那么dp[5]就定义好了,也就是dp[4] + dp[3].  那么dp[4]是什么呢?dp[4],也就是先退一步,或者是先退两步,把这两种方案的和,也就是dp[3] + dp[2]. 以此类推,dp[i]代表的是走i 层台阶的解决方案。

2 如何确定转移方程?

其实在确定dp[ ]数组的时候已经推导出来了,也就是dp[i] = dp[i - 1] + dp[i - 2]. 具体含义就是上i层台阶,先退一步,或者是先退两步,把这两种方案的和加起来。

3 如何确定初始值和边界条件?

初始值也就是上一阶台阶的解决方案和上两阶台阶的解决方案,f(1) = 1,f(2) = 2.

边界条件是什么?这个求最优解的时候会用到,爬楼梯不涉及最优解,具体可以看LeetCode.322 零钱兑换这道题目。

4 如何确定计算顺序?

一般是从小到大去计算的,因为此题f(1),f(2)已经是已知的了。如果是从大到小计算的话,那么要保证之前的值已经计算出来了。

具体代码如下:

class Solution {

    public int climbStairs(int n) {
        if(n <= 2)return n;
        int[]dp = new int[n + 1];
        dp[1] = 1;
        dp[2] = 2;
        for(int i = 3;i <= n;i++){
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
}

其实还是可以优化的,我去计算第N级台阶的方案时候,我只需要知道N - 1和N - 2级台阶的解决方案,那么N - 1和N - 2级台阶的解决方案不需要用数组来保存,只需要用临时变量保存即可,这样就优化了空间效率。

具体代码如下:

class Solution {

    public int climbStairs(int n) {
        if(n <= 2)return n;
        
        int step_one_result = 1;
        int step_two_result = 2;
        int step_n_result = 0;

        for(int i = 3;i <= n;i++){
            step_n_result = step_one_result + step_two_result;
            step_one_result = step_two_result;
            step_two_result = step_n_result;
        }
        return step_n_result;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值