Leetcode-70: Climbing Stairs (Fibonacci, DP, 尾递归,Memorization)

这题虽然简单,但非常经典。可供推敲处甚多。
解法1: DP (时间复杂度O(n)),空间复杂度O(n))。

#include <iostream>
#include <vector>
#include <cstring>

using namespace std;
int climbStairs(int n) {
    vector<int> dp(n+1, 1);    //we can also use int *dp=new int[n];     dp[0]=1; dp[1]=1;
    for (int i=2; i<=n; i++)
        dp[i]=dp[i-1]+dp[i-2];
    return dp[n];
}

int main()
{
    cout<<climbStairs(2)<<endl;
    cout<<climbStairs(3)<<endl;
    return 0;
}

解法2: 迭代 (时间复杂度O(n),空间复杂度O(1))

int climbStairs(int n) {
    if ((n==0) || (n==1)) return 1;
    int prev1=1, prev2=1;    //prev1: # of 1 step before, prev2: # of 2 steps before
    int result=0;
    for (int i=2; i<=n; ++i) {
        result=prev1+prev2;
        prev2=prev1;
        prev1=result;
    }
    return result;
}

解法3:递归
注意这里如果写成

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

肯定超时,因为计算climbStairs(n-1)的时候climbStairs(n-2)还没算,反过来也一样。这里时间复杂度是指数级别。
那怎么用递归呢?就我知道的有两种方法:尾递归Memorization
尾递归法: 时间复杂度O(n),空间复杂度O(1)。

int tail_recursion(int n, int a, int b) {
    if ((n==0) || (n==1)) return b;
    return tail_recursion(n-1, b, a+b);
}

int climbStairs(int n) {
    return tail_recursion(n, 1, 1);
}

下面这个关于尾递归的link不错:
https://blog.csdn.net/zcyzsy/article/details/77151709

当递归调用是整个函数体中最后执行的语句,且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归 (具体什么叫“返回值不属于表达式的一部分”可以参考上面的link里面的两个例子,第一个返回值里面带了一个n,每次调用产生的栈帧需要保存在栈上知道下一个子调用的返回值确定)。
尾递归函数在递归展开后不再做任何操作,所以该函数可以不等子函数执行完,自己就直接销毁,这样就不再占用内存。所以一个递归深度O(n)的尾递归函数,可以做到只占用O(1)空间,大大优化了栈空间的利用。
尾递归的精髓就是通过参数传递结果,达到不压栈
但是玩尾递归要注意: C++的时候有时候最后一个函数是析构函数,跟表面上看起来不一样。

Memorization法:又叫记忆化搜索,就是用一个全局数组来存储中间的状态。全局数组先初始化为0,这样程序在运行的时候根据某个数组元素的值就可以知道它是不是已经计算过了。
时间复杂度O(n),空间复杂度O(n)。

vector<int> nums;
int helper(int n) {
    if (nums[n]>0)
        return nums[n];
    else {
        nums[n]=helper(n-1)+helper(n-2);
        return nums[n];
    }
}

int climbStairs(int n) {
    nums.resize(n+1);
    memset(&nums[0], 0, sizeof(int)*nums.size());   //note the usage of &nums[0]. Also, we can use fill(nums.begin(), nums.end(), 0);
    nums[0]=1; nums[1]=1;
    return helper(n);
}

另外,顺便总结一些递归的一些要点:
1) 递归的顺序与迭代相反。递归是从后往前,迭代是从前往后。
2) 递归层数较多会导致stack overflow, 因为每次函数调用都会消耗一些栈空间,具体在以下3个地方:
a. 函数输入参数(e.g., 4个字节)
b. 函数返回值(e.g., 4个字节)
c. 局部变量
3) 递归3要素:
a. 递归定义: 函数接受什么样的参数,返回什么样的值,代表什么意思
b. 递归的拆解
c. 递归的出口(即什么时候返回)

还有两个NB解法,下次再细看:
解法4: Binets Method (时间复杂度O(logn),空间复杂度O(1))。
解法5:公式法(时间复杂度O(1),空间复杂度O(1))。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值