假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
public int climbStairs(int n) {
if(n==2) return 2;
if(n==1) return 1;
return climbStairs(n-2)+climbStairs(n-1);
}
超出时间限制了
public intc limbStairs(intn){
if(n <= 2){return n;}
int[] dp = new int[n];
dp[0] = 1;
dp[1] = 2;
for(int i = 2; i < n; i++){
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n-1];
}
继续优化空间复杂度(一维数组变成三个变量)
class Solution {
public int climbStairs(int n) {
int p = 0, q = 0, r = 1;
for (int i = 1; i <= n; ++i) {
p = q;
q = r;
r = p + q;
}
return r;
}
}
复杂度分析
时间复杂度:循环执行 n次,每次花费常数的时间代价,故渐进时间复杂度为 O(n)。
空间复杂度:这里只用了常数个变量作为辅助空间,故渐进空间复杂度为 O(1)。
如果观察数学规律,可知本题是斐波那契数列,那么用斐波那契数列的公式即可解决问题
public class Solution {
public int climbStairs(int n) {
double sqrt5 = Math.sqrt(5);
double fibn = Math.pow((1 + sqrt5) / 2, n + 1) - Math.pow((1 - sqrt5) / 2, n + 1);
return (int) Math.round(fibn / sqrt5);
}
}
时间复杂度:power的时间复杂度O(logn)
补充:求power的时间复杂度
2^8
4^4
16^2
256
int power(int a, int n) //a^n
{
int ans = 1;
while(n > 0)
{
if(n&1) //当n为奇数时,乘以余下的一个a
ans *= a;
a *= a;
n /= 2;
}
return ans;
}
int power(long long a, int n)
{
long long ans = 1;
while(n > 0)
{
if(n&1)
{ //(ans*a)%mod
ans *= a;
ans %= mod;
}
//(a*(a%mod))%mod
a *= a%mod;
a %= mod;
n >> 1;
}
return ans%mod;
}
这里形成的数列正好是斐波那契数列,答案要求的 f(n)即是斐波那契数列的第 n 项(下标从 0开始)。我们来总结一下斐波那契数列第 n 项的求解方法:
- n比较小的时候,可以直接使用过递归法求解,不做任何记忆化操作,时间复杂度是 O(2^n),存在很多冗余计算。
- 一般情况下,我们使用「记忆化搜索」或者「迭代」的方法,实现这个转移方程,时间复杂度和空间复杂度都可以做到 O(n)。
- 为了优化空间复杂度,我们可以不用保存 f(x - 2) 之前的项,我们只用三个变量来维护 f(x)、f(x - 1) 和 f(x - 2),你可以理解成是把「滚动数组思想」应用在了动态规划中,也可以理解成是一种递推,这样把空间复杂度优化到了 O(1)。
- 随着 n 的不断增大 O(n)可能已经不能满足我们的需要了,我们可以用「矩阵快速幂」的方法把算法加速到 O(logn)。
- 我们也可以把 n 代入斐波那契数列的通项公式计算结果,但是如果我们用浮点数计算来实现,可能会产生精度误差。