题目:[题目及部分解法来自力扣]
-
假设你正在爬楼梯。需要
n
阶你才能到达楼顶。每次你可以爬
1
或2
个台阶。你有多少种不同的方法可以爬到楼顶呢?(1 <= n <= 45) -
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。(0 <= n <= 100)
solution one:
使用动态规划来处理斐波那契数列问题,最好理解,同时也最好处理!
当只有一个台阶的时:1 (1种爬法)
当只有二个台阶的时:1 + 1/ 2 (2种爬法)
当只有三个台阶的时:1 + 1 + 1/ 1 + 2 / 2 + 1 (3种爬法)
当只有四个台阶的时:1 + 1 + 1 + 1 / 1 + 2 + 1 / 1 + 1 + 2 / 2 + 1 + 1/ 2 + 2 (5种爬法)
有木有像斐波那契数列??没有自己再列出几个数哈哈哈。
们用 f(x) 表示爬到第 x 级台阶的方案数,考虑最后一步可能跨了一级台阶,也可能跨了两级台阶,所以我们可以列出如下式子:
f(x) = f(x - 1) + f(x - 2)
它意味着爬到第 x 级台阶的方案数是爬到第 x - 1级台阶的方案数和爬到第 x - 2级台阶的方案数的和。
(注意,题目1和题目2只是n的起始值不,一个从0开始,一个从1开始!)
//青蛙跳台阶
public final int MOD = 1000000007;
public int numWays(int n) {
if(n < 2) return 1;
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2;i <= n;i++){
dp[i] = (dp[i - 1] + dp[i - 2]) % MOD;
}
return dp[n];
}
//爬楼梯
public final int MOD = 1000000007;
public int climbStairs(int n) {
int[] dp = new int[n+1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i <= n; i ++){
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}
solution two:
这里给出非递归方法破解以上题目的方法,只是起始值不同。
// 青蛙跳台阶(0 <= n <= 100)
public int numWays(int n) {
int a = 1, b = 1, sum;
for(int i = 0; i < n; i++){
sum = (a + b) % 1000000007;
a = b;
b = sum;
}
return a;
}
// 爬楼梯(1 <= n <= 45)
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;
}
solution three:
对于从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);
}
}
solution four:
矩阵快速幂
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;
}
总结
这里形成的数列正好是斐波那契数列,答案要求的 f(n) 即是斐波那契数列的第 n 项(下标从 0 开始)。我们来总结一下斐波那契数列第 n 项的求解方法:
- n 比较小的时候,可以直接使用过递归法求解,不做任何记忆化操作,时间复杂度是 O(2^n),存在很多冗余计算。
- 一般情况下,我们使用「记忆化搜索」或者「迭代」的方法,实现这个转移方程,时间复杂度和空间复杂度都可以做到 O(n)。
- 为了优化空间复杂度,我们可以不用保存 f(x - 2)f(x−2) 之前的项,我们只用三个变量来维护 f(x)、f(x - 1)和 f(x - 2),你可以理解成是把「滚动数组思想」应用在了动态规划中,也可以理解成是一种递推,这样把空间复杂度优化到了 O(1)。
- 随着 n 的不断增大 O(n) 可能已经不能满足我们的需要了,我们可以用「矩阵快速幂」的方法把算法加速到 O(\log n)。
- 我们也可以把 n 代入斐波那契数列的通项公式计算结果,但是如果我们用浮点数计算来实现,可能会产生精度误差。