假设你正在爬楼梯。需要 n 阶你才能到达楼顶。——小青蛙跳台阶
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2 输出: 2 解释: 有两种方法可以爬到楼顶。 1. 1 阶 + 1 阶 2. 2 阶
示例 2:
输入: 3 输出: 3 解释: 有三种方法可以爬到楼顶。 1. 1 阶 + 1 阶 + 1 阶 2. 1 阶 + 2 阶 3. 2 阶 + 1 阶
1、暴力法搜索求解
/*
1、暴力法:把所有可能爬的阶数进行组合,i为当前阶数,n为目标阶数
climbStairs(i,n) = (i+1,n) + climbStairs(i+2,n);
时间复杂度:O(2^n),树形递归的大小为 2^n,有大量的重复计算
空间复杂度:O(n),递归树的深度可以达到n
*/
import java.util.*;
public class ClimbStairsFirst {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int result = climbstairs(0, n);
System.out.println(result);
}
private static int climbstairs(int i, int n) {
// TODO Auto-generated method stub
if(i > n) {
return 0;
}
if(i == n) {
return 1;
}
return climbstairs(i + 1, n) + climbstairs(i + 2, n);
}
}
2、记忆化递归,对1中方法的改进
/*
2、记忆化递归:解决冗余问题,将中间值进行存储,直接取值不进行重复计算
加内存。递归状态减少,多了一个数组。
时间复杂度:O(n),树形递归的大小为 n
空间复杂度:O(n),递归树的深度可以达到n
*/
import java.util.*;
public class ClimbStairsSecond {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] memory = new int[n+1];
int result = climbstairs(0, n, memory);
System.out.println(result);
}
private static int climbstairs(int i, int n, int[] memory) {
// TODO Auto-generated method stub
if(i > n) {
return 0;
}
if(i == n) {
return 1;
}
if(memory[i] > 0) {
return memory[i];
}
memory[i] = climbstairs(i + 1, n, memory) + climbstairs(i + 2, n, memory);
return memory[i];
}
}
3、动态规划
/*
3、动态规划
这个问题可以被分解为一些包含最优子结构的子问题,即他的最优解可以由子问题的
最优解来构建。
第i阶可以由一下两种方法得到:
在第(i-1)阶后向上爬一阶;
在第(i-2)阶后向上爬两阶。
所以到达第i阶的方法总数就是到第(i-1)阶和第(i-2)阶的方法数之和。
令dp[i]表示能到达第i阶的方法总数:
dp[i] = dp[i-1] + dp[i-2]
时间复杂度:O(n),单循环用到了n
空间复杂度:O(n),dp数组用了n的空间
*/
import java.util.Scanner;
import java.util.*;
public class ClimbStairsDp {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int result = climbstairsDp(n);
System.out.println(result);
}
// 动态规划
private static int climbstairsDp(int n) {
// TODO Auto-generated method stub
if(n== 0 || n == 1) {
return n;
}
int[] dp = new int[n+1];
// dp[0] = 1; // 不管怎样,数组下标指针肯定是从0开始的,所以要考虑0.有0个台阶,不需要爬,所以没有方法数(但从斐波那契角度,dp[0]=1)
dp[1] = 1; // 一阶的方案
dp[2] = 2; // 二阶的方案
for(int i = 3; i <= n; i ++) {
dp[i] = dp[i -1] + dp[i - 2];
}
return dp[n];
}
}
4、斐波那契数:递推求解
/*
4、斐波那契数:递推求解
上述动态规划中的
dp[i] = dp[i-1] + dp[i-2]
其实就是第i个斐波那契数,即
Fib(n) = Fib(n-1) + Fib(n-2);
Fib(0) = 1; —> 这里可以考虑,也可以不考虑
Fib(1) = 1;
Fib(2) = 2.
时间复杂度:O(n),单循环用到了n,需要计算第n个斐波那契数
空间复杂度:O(1),使用常量级的空间迭代
*/
import java.util.Scanner;
public class ClimbStairsFibNum {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int result = climbstairsFib(n);
System.out.println(result);
}
// 斐波那契数
private static int climbstairsFib(int n) {
// TODO Auto-generated method stub
if(n == 0 || n == 1) {
return n;
}
int fib1 = 1;
int fib2 = 2;
for(int i = 3; i <= n; i ++) {
int fib3 = fib1 + fib2;
fib1 = fib2;
fib2 = fib3;
}
return fib2;
}
}
5、斐波那契递推数列
import java.util.Scanner;
public class ClimbStairsFibFormula {
public static void main(String[] args) {
// TODO Auto-generated method stub
/*
5、斐波那契数列:直接公式求解
时间复杂度:O(1ogn)
空间复杂度:O(1),使用常量级的空间迭代
*/
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int result = climbstairsFib(n);
System.out.println(result);
}
// 递推公式
private static int climbstairsFib(int n) {
// TODO Auto-generated method stub
// 斐波那契数列: 初始化f(1) = 1; f(2) = 1;
// 因为斐波拉切数列是1,1,2,3,5......这里相当于少了最前面那个1,所以要往后移1位
double sqrt5 = Math.sqrt(5);
double fibN = Math.pow((1+sqrt5) / 2, n+1) - Math.pow((1-sqrt5) / 2, n+1);
return (int) (fibN / sqrt5);
}
}
https://www.zhihu.com/question/25217301
6、矩阵快速幂
<待补充>
7、总结
答案要求的 f(n) 即是斐波那契数列的第 n 项(下标从 0开始)。
斐波那契数列第 n 项的求解方法:
(1)n 比较小的时候,可以直接使用过递归法求解,不做任何记忆化操作,时间复杂度是 O(2^n),存在很多冗余计算。
(2) 一般情况下,使用「记忆化搜索」或者「迭代」的方法,实现转移方程,时间复杂度和空间复杂度都可以做到 O(n)。
(3)为了优化空间复杂度,可以不用保存 f(x - 2)之前的项,只用三个变量来维护 f(x)、f(x - 1) 和 f(x - 2),可以理解成将把「滚动数组思想」应用在了动态规划中,也可以理解成是一种递推,这样把空间复杂度优化到了 O(1)。
(4)随着 n 的不断增大 O(n) 可能已经不能满足需求,可以用「矩阵快速幂」的方法把算法加速到 O(logn)。
(5)同时可以把 n 代入斐波那契数列的通项公式计算结果,但是如果用浮点数计算来实现,可能会产生精度误差。