17 . 爬楼梯

题目介绍

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 12 个台阶。你有多少种不同的方法可以爬到楼顶呢?

提示:

  • 1 <= n <= 45
cpp 复制代码
class Solution {
public:
    int climbStairs(int n) {
        
    }
};

原题链接: 70. 爬楼梯 - 力扣(LeetCode)


解析

1 . 本题需求很明确,但是初次接触这种类型的小伙伴知晓怎么自己推导答案,不知背后蕴藏的算法

2 . 它给的代码,参数只有 n 个阶梯,要求返回走到n个阶梯的方法

3 . 对于先走1步还是后走1步的方法,都是算作不同方法

4 . 这意味着,拿到一个要走到n级阶梯这个目标,每次出发都有两个选择:左一个是走1步,右一个是走2步

5 . 你能想到什么数据结构呢

递归------青春版

1 . 数组排除,链表也不行。都是顺序结构

2 . 非树型结构莫属,而且也只有两个分叉------摘下面具,就是最熟悉的二叉树

3 . 二叉树与递归就好比🐟和水

4 . 这棵二叉树的每个结点就是一步台阶或者两步台阶

5 . 再看递归------老两样,核心任务和递归出口

核心任务:确定当前树要完成的任务(往往结合参数和返回值设计)

递归出口:对传入的参数进行审判
核心任务

a . 现在n就是当前树,表示现在已经走到n级台阶了,因为给的参数就是n

b . 核心任务就是计算当前树(台阶数)的方法数

c . 我们从n出发,接下来要么n-2 要么n-1 ------确定了左右子树


图1 举例

1)当2为当前树,计算出走到2的方法数;当1为当前树,计算出走到1台阶的方法数

2)当3为当前数,当前树的核心任务就是计算出走到3阶梯的方法数 = 左子树方法数 + 右子树的方法数

d . 当前树往往用当前结点表示,此处亦然。用当前走到的台阶表示

e . 需要再加个变量记录当前树的方法数否?不需要,返回值为int,直接返回就行

cpp 复制代码
class Solution {
public:
    int climbStairs(int n) {
        return climbStairs(n-1)+climbStairs(n-2);// 核心任务,一步到位
    }
};

递归出口

a . 当向下走,不断走1步,或者走2步。

1)n == 0 说明这个方法可行,返回1

2)n < 0 说明这个方法不可行,返回0

注:所以说白:就是在计数整棵树有效的叶子结点。当叶子结点为0有效,向上开始返回1

当然这个认知,也是在从推导完核心任务和递归出口,自然而然得出的

完整参考代码:

cpp 复制代码
class Solution {
public:
    int climbStairs(int n) {
        if(n == 0) return 1;
        if(n < 0) return 0;
        return climbStairs(n-1)+climbStairs(n-2);
    }
};

递归------工作留痕版

1 . 《递归------青春版》的方法,清晰明了------但一旦碰上n大的数,比如:44就会超过时间限制

2 . 难道有无用功出现吗

图3 无用功

a . 比如图上,我们会两次走到1这棵树,但是在1台阶的方法数都是左0右1,如果不拦着程序递归,它就会再去计算一遍1台阶的方法数

b . 有没有方法能让当前树如果遇到重复,就立马返回它曾经记录下的方法数

c . 不妨创建个备忘录数组

memo[n+1] 大小为n+1 。存储的元素为 对应阶梯的方法数

d . 因为备忘录数组需要初始化,那不妨把递归逻辑放在下层

cpp 复制代码
class Solution {
public:
    int _climbStairs(int n,vector<int>& memo)
    {
        
        
    }
    int climbStairs(int n) {
       vector<int> memo(n+1,-1);
       return _climbStairs(n,memo);
    }
};

注:

1 . vector<int> memo(n+1,-1);初始化出n+1个格子,下标范围为0~n

2 . 格子里都初始化为-1

3 . 于是,递归逻辑里加上一本备忘录。记得传vector<int>& ,输出型参数的好处还记得否?减少拷贝,原地修改参数

cpp 复制代码
int _climbStairs(int n,vector<int>& memo)
    {
         if(n == 0) return 1;
        if(n < 0) return 0;
        if(memo[n] != -1) return memo[n]; // 如果以前遇到过这个阶梯,掏出备忘录,直接返回当前结点的方法数
        memo[n] = _climbStairs(n-1,memo)+_climbStairs(n-2,memo);// 先更新备忘录
        return memo[n];// 再返回
    }

完整参考代码:

cpp 复制代码
class Solution {
public:
    int _climbStairs(int n,vector<int>& memo)
    {
         if(n == 0) return 1;
        if(n < 0) return 0;
        if(memo[n] != -1) return memo[n];
        memo[n] = _climbStairs(n-1,memo)+_climbStairs(n-2,memo);
        return memo[n];
    }
    int climbStairs(int n) {
       vector<int> memo(n+1,-1);
       return _climbStairs(n,memo);
    }
};

动态规划------自下而上

1 . 不要被"动态规划"唬住,先看我接下来怎么说,慢慢去感受"动态规划"

2 . 如果说递归是从第n节台阶不断往下走,每次走1步或者2步,最后遇到有效的结点才算一个方法

3 . 那么动态规划就是直接从底部出发,不断计算出 台阶2,3,4,5,6......的方法数

cpp 复制代码
class Solution {
public:
    int climbStairs(int n) {
        vector<int> dp(n+1);// dp[i]表示i台阶的总方法数
        dp[0] = 1;
        dp[1] = 1;
        for(int i = 2;i <= n;i++)
        {
            dp[i] = dp[i-1]+dp[i-2];// 这里不要迷糊,就是dp[i]就是dp[i-1]+dp[i-2]
        }
        return dp[n];
    }
};

注:

1 . dp[i]等于i-1台阶的方法数加上i-2的方法数

2 . 你可以在脑海里想象,我们i-1台阶的方法数就是i-1那棵树下所有的有效结点

3 . dp[i-2] 也是同样的道理

4 . 起点dp[0] 为1,不动 (这个比较特殊;dp[1]只有0->1一种方法

总结以及完整参考代码:

总结暂时放一下,博主赶个路😣

cpp 复制代码
class Solution {
public:
    int climbStairs(int n) {
        if(n == 0) return 1;
        if(n < 0) return 0;
        return climbStairs(n-1)+climbStairs(n-2);
    }
};
cpp 复制代码
class Solution {
public:
    int _climbStairs(int n,vector<int>& memo)
    {
         if(n == 0) return 1;
        if(n < 0) return 0;
        if(memo[n] != -1) return memo[n];
        memo[n] = _climbStairs(n-1,memo)+_climbStairs(n-2,memo);
        return memo[n];
    }
    int climbStairs(int n) {
       vector<int> memo(n+1,-1);
       return _climbStairs(n,memo);
    }
};
cpp 复制代码
class Solution {
public:
    int climbStairs(int n) {
        vector<int> dp(n+1);// dp[i]表示i台阶的总方法数
        dp[0] = 1;
        dp[1] = 1;
        for(int i = 2;i <= n;i++)
        {
            dp[i] = dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
};
cpp 复制代码
class Solution {
public:
    void dfs(int n,int& way,vector<int>& memo)
    {
        if(n < 0) return;
        if(n == 0) {
            way++;
            return;
        }
        if(memo[n]!=-1)
        {
            way += memo[n];
            return;
        }
        int tmp = way;
        dfs(n-2,way,memo);
        dfs(n-1,way,memo);
        memo[n] = way - tmp;
    } 
    int climbStairs(int n) {
        int way = 0;
        vector<int> memo(n+1,-1);
        dfs(n,way,memo);
        return way;
    }
};// 此方法其实是 工作留痕版------再加了个输出型参数,记录当前台阶的方法数way。
//于是就删掉了递归的返回值,通过way进行返回

还有两篇,燥候------

相关推荐
ACERT3332 小时前
03矩阵理论复习-内积空间和正规矩阵
算法·矩阵
肥猪猪爸2 小时前
TextToSql——Vanna的安装与使用
人工智能·python·算法·机器学习·大模型·ollama·vanna
谈笑也风生3 小时前
经典算法题详解之切分数组(一)
数据结构·算法·leetcode
松涛和鸣3 小时前
28、Linux文件IO与标准IO详解:从概念到实战
linux·网络·数据结构·算法·链表·list
长安er3 小时前
LeetCode 167/15 两数之和与三数之和-双指针1
算法·leetcode·双指针·两数之和·三数之和
Hello娃的3 小时前
【神经网络】反向传播BP算法
人工智能·神经网络·算法
lynnlovemin3 小时前
从暴力到高效:C++ 算法优化实战 —— 排序与双指针篇
java·c++·算法
jinxinyuuuus3 小时前
快手在线去水印:短链解析、API逆向与视频流的元数据重构
前端·人工智能·算法·重构
Flash.kkl3 小时前
优先算法专题十五——BFS_FloodFill
算法·宽度优先