橘子刷题第一题之爬楼梯

题目:本题出自力扣第70题
设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2表示总共两阶楼梯
输出:2表示一共两种爬法
解释:有两种方法可以爬到楼顶。
方法1:第一次爬1 阶 + 第二次还爬1 阶
方法2.:直接爬两阶
示例 2:
输入:n = 3表示总共三阶楼梯
输出:3表示一共三种爬法
解释:有三种方法可以爬到楼顶。
方法1:1 阶 + 1 阶 + 1 阶
方法2:1 阶 + 2 阶
方法3:2 阶 + 1 阶

解法1:递归

我们这里来分析一下这个题目,其实你想想,不管总共多少阶楼梯。
也不管你每次走多少个阶梯(我们这里只限制每次要么一个要么两个,三个可能扯着淡)。
你每次走完一个操作,剩下你面对的还是上一次面对的爬楼梯,只不过是剩下的台阶比之前少了一些而已。
这就是一个典型的递归操作,既然我们已经知道是递归了,那么别说了,递归两步走。找递归公式,找出口。
1、递归公式
	我们每次面对楼梯的时候,都有两种选择,一个是爬一个,一个是爬两个。当你爬一个的时候,
	你的总共爬法就是f(n-1),意思就是我第一次走了一个,剩下的不就是f(n-1)吗,再进一步意思
	就是我的函数就是f,剩下的不就是f(n-1),有点啰嗦了,但是就是这么个意思。
	第二个就是我爬两个,这种情况的时候就有f(n-2)这种,所以综合上述,其实我们从一开始的时候
	就有两个爬法,就是f(n-1) + f(n-2)而我们每次爬的时候面对下一步的时候,还是这两个部分组成
	只不过你传进去的n不一样了。这就是递归公式。
2、递归出口
	众所周知,递归是个入栈出栈操作,你要是没个出口,一直入栈,最后的结果就是溢出,基本GG。
	你还说个锤子。递归的出口其实就是找到一个你什么时候结束操作的时机。
	那么我们把自己放到楼梯上,你什么时候不用考虑公式,而是有机会直接就完成爬楼,结束操作了呢?
	这个其实不难想到,你每次要么爬一个,要么爬两个,所以最后其实你在只剩一个或者两个的时候就
	能直接结束了。所以我们最后拿到了结束出口,就是n=1 或者 n=2的时候。于是答案呼之欲出。

爬楼梯递归函数

// 递归的代码就很好写出来了
class Solution {
    public int climbStairs(int n) {
        if(n == 1) return 1;// 出口1
        if(n == 2) return 2;// 出口2
        return climbStairs(n - 1) + climbStairs(n - 2); // 递归往下走
    }
}

但是我们把它放到力扣里面执行就看到。
在这里插入图片描述
好了,寄了。因为此时的时间复杂度已经是O(n)了,很容易就出现了超时这种操作,力扣会设置的边界条件比较苛刻。你平时写代码层数不多的时候基本不会有这个问题。
那么问题出在哪里,我们想一下,假如我以6阶楼梯为例,我要求f(6)的时候毕然要求f(5)和f(4),要求f(5)的时候也要求f(4)其余的也有这个情况往下推,存在着大量的重复计算,这就是问题。影响你的时间复杂度。
既然我们知道问题是你每次都去计算,但是实际上不是每次都要计算的。我们只需要搞一个map,每次算出来的值看看map里面有吗,没有放进去,有直接拿出来用,能省就省点,但是需要你开辟新的map,其实也是个空间换时间的体现。

解法2:进阶版递归

class Solution {
	// 声明出一个map
    Map<Integer,Integer> map = new HashMap<>();
    public int climbStairs(int n) {
        if(n == 1) return 1;// 出口1
        if(n == 2) return 2;// 出口2
        if(map.get(n) != null){// map判断
            return map.get(n);// 存在直接拿,不用计算
        }else{
            int result = climbStairs(n -1) + climbStairs(n - 2);// 不存在就计算
            map.put(n,result);// 算完放进去
            return result;// 递归往下走
        }
    }
}

在这里插入图片描述
我们看到通过了,但是因为声明了一个map作为辅助,空间复杂度上拉了一点。但是总归是通过了。
但是众所周知,你凡是能用递归解决的方式其实也能用循环迭代解决。递归存在这种栈过深的问题,那我们就用迭代来规避这个问题,而且递归的代码,说实话不好看,简练但是不好理解。它是一种计算机的逻辑思想,和人的那种思维差点意思。

解法3:循环迭代

OK,我们来看一下递归这个操作,递归这个操作实际上是两步,一个是递,一个是归。
递就是你往下递出,往下走,走到尽头也就是出口。然后去做归,把你在递的过程中拿到的值一步步往上归,归到顶端最后返回。
所以它其实是一个从上往下计算的一个操作。
那我们看一下这个图示:
在这里插入图片描述
这个图就是一个组合结构,我们已知了f2和f1的值,就能直接计算出f3,然后计算f4又需要用到前面的f2,f3,计算f5又能用到上一步的f4和f3。所以我们看到其实你每次从下面往上走的时候需要用到上一步的两个值,那么其实想一下就是我们用到两个变量保存上面的值,来计算这个值。

class Solution {
    public int climbStairs(int n) {
        if(n == 1) return 1;// 直接返回
        if(n == 2) return 2;// 直接返回
        int pre = 2;// 前一个值
        int prePre = 1;// 前一个的前一个值
        int result = 0;// 返回结果
        for(int i = 3;i <= n;i++){// 开始遍历,因为1,2都有了,所以直接从3开始遍历
            // 保存前两个值,开始求和
            result = pre + prePre;
            prePre = pre;
            pre = result;
        }
        // 返回结果
        return result;
    }
}

在这里插入图片描述
OK,我们至此昨晚了分析,那么可以看到,其实递归就是你存在规模变化但是操作路数不变的一个场景。你就可以考虑递归解法。
1、递归要做的就是找到递归公式,找法也简单,就是你从第一步开始看看有哪些情况组成了你的结果,加起来就是了。
上来先来个f(n),看看有哪些组成,直接加。
然后就是找出口,出口更简单,就是你啥时候不要看你的递归公式分析了,直接能跳出去,哪些符合哪些就是出口了。
2、其次你递归要是存在重复计算,是实际上不管是不是递归只要存在重复计算就能用空间换时间,map这种O(1)put和get的实在太尼玛合适了。
3、再有就是只要你递归就能迭代循环解决,递归是从上往下找到出口再往上,迭代就是从出口开始从下往上循环,一步步把你的中间值加上去,直到最高层就出去了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值