LeetCode70 爬楼梯(面试遇到2次)

64 阅读3分钟

leetcode.cn/problems/cl…

image.png

这道题博主今年在面试中就遇到过两次,分别是 富途LiblibAI

其实是一道简单题,但是要求你理解递归,回溯的核心思想,或者动态规划的优化解法,以及能分析出时空复杂度

解法一:回溯法(超时)

套用我们前面学习的框架即可

func climbStairs(n int) int {
    var res int
    total := 0
    backtrack(n, total, &res)
    return res
}

func backtrack(n int, total int,res *int){
    if total >= n{
        if total == n{
            *res++
        }
        return
    }
    // 本次选择走一步
    total+=1
    backtrack(n, total, res)
    total-=1
    // 本次选择走2步
    total+=2
    backtrack(n, total, res)
    total-=2
}

递归算法的时间复杂度怎么计算?就是用子问题个数乘以解决一个子问题需要的时间。

首先计算子问题个数,即递归树中节点的总数,每个节点有2个分叉,分别决定走一步还是走2步。显然二叉树节点总数为指数级别,所以子问题个数为 O(2^n)。

然后计算解决一个子问题的时间,在本算法中,没有循环,只有一个简单的步数加法操作,时间为 O(1)。

所以,这个算法的时间复杂度为二者相乘,即 O(2^n),指数级别,时间复杂度太高了。

空间复杂度即为递归栈空间大小,O(n)。

解法二:动态规划(自顶向下的递归+备忘录)

核心是找到状态转移方程

func climbStairs(n int) int {
    memo := make([]int, n+1) 
    return dp(memo, n)
}

func dp(memo []int, n int) int{
    if n <= 2{
        return n
    }
    if memo[n] > 0{ // 备忘录避免重复计算
        return memo[n]
    }
    // 状态转移方程
    // 爬到第 n 级台阶的方法个数等于爬到 n - 1 的方法个数 + 爬到 n - 2 的方法个数
    memo[n] = dp(memo, n-1) + dp(memo, n-2)
    return memo[n]
}

时间复杂度:O(n)。由于每个状态只会计算一次,动态规划的时间复杂度 = 状态个数 × 单个状态的计算时间。本题状态个数等于 O(n),单个状态的计算时间为 O(1),所以动态规划的时间复杂度为 O(n)。

空间复杂度:O(n),备忘录大小。

解法三:动态规划(自底向上的递推法)

func climbStairs(n int) int {
    dp := make([]int, n+1)
    dp[0] = 1
    dp[1] = 1
    for i:=2; i<len(dp); i++{
        dp[i] = dp[i-1] + dp[i-2]
    }
    return dp[n]
}

时间复杂度:O(n),一层循环。

空间复杂度:O(n),额外申请的dp数组空间

优化空间

观察状态转移方程,发现一旦算出 dp[i],那么 dp[i−2] 及其左边的状态就永远不会用到了。

意味着每次循环,只需要知道「上一个状态」和「上上一个状态」的dp值,可以直接用两个变量记录即可(dp1, dp2),初始值均为1,在循环中不断更新

func climbStairs(n int) int {
    dp_i_1, dp_i_2 := 1, 1 // 分别代表dp[i-1]和dp[i-2]
    for i:=2; i<=n; i++{
        dp_i := dp_i_1 + dp_i_2
        dp_i_2 = dp_i_1
        dp_i_1 = dp_i
    }
    return dp_i_1 // 因为最后一次循环把dp_i赋值给了dp_i_1
}

时间复杂度:O(n),一层循环。 空间复杂度:O(1)