青训营X豆包MarsCode AI刷题第124题(中等) | 豆包MarsCode AI刷题

46 阅读4分钟

题目序号:124

题目难度:中等

问题描述:

小U最近决定挑战一座非常高的楼梯,每次他可以选择走一步或两步,但有一个重要的限制:他不能连续走两步。因此,小U想知道他总共有多少种不同的方式可以从楼梯的底部走到顶端。

你需要帮他计算在给定的楼梯层数下,小U有多少种走法。

1.题目理解

这个问题是一个经典的动态规划问题,我们可以使用递归的方式来解决。我们定义一个函数 f(n) 来表示到达第 n 层楼梯的走法数量。根据题目描述,小U每次可以选择走一步或两步,但不能连续走两步。因此,我们可以得到以下递推关系。其中要着重理解 “不能连续走两步” 这个限制

(1)如果小U在第 n 层,他可以从第 n-1 层走一步上来,或者从第 n-2 层走两步上来。但是,如果他从第 n-2 层走两步上来,那么他不能从第 n-3 层走两步上来,因为这样就违反了不能连续走两步的规则。所以,我们有: f(n)=f(n−1)+f(n−3),即我们可以从上一个台阶选择走一步上来,也可以选择从上三个台阶选择走1步加2步或者2步加1步,这样就能够避免连续走两步。这也是本题最难的地方,不能使用简单的f(n) = f(n-1) + f(n-2)

(2)但是,当小U在第1层或第2层时,他不能从更低的楼层走两步上来,所以我们有:f(1)==1;f(2)==2;f(3)==3

2.代码

public class Main {
    public static int solution(int n) {
        // Edit your code here
        int[] dp =new int[n+1];
        if(n==1){
            return 1;
        }else if(n==2){
            return 2;
        }else if(n==3){
            return 3;
        }
        else{
            dp[0]=0;
            dp[1]=1;
            dp[2]=2;
            dp[3]=3;
            for(int i=4;i<=n;i++){
                dp[i]=dp[i-1]+dp[i-3];
            }
            return dp[n];
        }
    }

    public static void main(String[] args) {
        // Add your test cases here
        
        System.out.println(solution(2) == 2);
    }
}

3.代码讲解

对于这道题目,只要理解了动态规划的状态转移方程起始代码就不难理解了,首先我们初始化一下dp数组,然后给第一层第二层和第三层都赋予初值,然后从第四层开始不断的执行状态转移方程dp[i]=dp[i-1]+dp[i-3];,并最终输出第n层的结果即可。

4.时间复杂度分析

本题只需要线性的遍历数组即可,所以时间复杂度显然为O(n),同理我们也能轻松的得出空间复杂度为O(n)。

5.相似例题

leetcode上面也有一道爬梯子的题目(第70题),只不过没有本题的限制条件,所以其实变得更加简单了。在本题中使用的动态规划方法在遇到较小的n时尚可,但是当n逐渐增大,O(n)也会显得捉襟见肘。那么我们看一下,如果不设不允许连续走两次两步这一限制,时间复杂度更小方法是什么。

在没有限制的情况下f(n) = f(n-1) + f(n-2),我们可以推导出如下矩阵,当我们看到矩阵的时候一定要想到快速幂算法(时间复杂度为O(logn))

image-20241126232257192转存失败,建议直接上传图片文件

具体代码如下

public class Solution {
    public int climbStairs(int n) {
        int[][] q = {{1, 1}, {1, 0}};
        int[][] res = pow(q, n);
        return res[0][0];
    }
​
    public int[][] pow(int[][] a, int n) {
        int[][] ret = {{1, 0}, {0, 1}};
        while (n > 0) {
            if ((n & 1) == 1) {
                ret = multiply(ret, a);
            }
            n >>= 1;
            a = multiply(a, a);
        }
        return ret;
    }
​
    public int[][] multiply(int[][] a, int[][] b) {
        int[][] c = new int[2][2];
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                c[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j];
            }
        }
        return c;
    }
}

快速幂的原理为:

\[n^{2^0} \cdot n^{2^0} = n^{2^1}, \quad n^{2^1} \cdot n^{2^1} = n^{2^2}, \quad n^{2^2} \cdot n^{2^2} = n^{2^3}, \quad n^{2^3} \cdot n^{2^3} = n^{2^4}\]

将原先的幂次变为2的次方再相乘,就可以将O(n)转化为O(logn)