0. 题目来源
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof
1. 题目描述
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
- 注意:答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
2. 题目分析与求解
斐波那契数列是非常常见的题目,常常用于学习递归,所以这个题目绝对可以用递归做,另外本题还可以用动态规划的思想做。
2.1 递归的方法
其实看到题目后,我啪的一下就敲出了如下代码,很快的啊,然后提交显示超时,很尴尬。
public class Solution {
public int fib(int n) {
if (n == 0 )
return 0;
if (n == 1)
return 1;
return (fib(n-1)+ fib(n-2))%1000000007;
}
}
究其原因,如下图所示,求F(6)时,F(2)求解了5次,F(3)求解了3次,F(4)求解了2次,重复率非常高,内存消耗量特别大。当求解的F(n)中的n越大,重复量越多。
故做出如下优化:
import java.util.HashMap;
import java.util.Map;
/**
* @author: sz
* @date: Created in 2021/1/31
* @description: 斐波那契数列,用map优化后不超时
* @version: 1.0
*/
public class Solution4 {
public int fib(int n) {
return fib(n,new HashMap<>());
}
private int fib(int n , Map<Integer,Integer> map){
if (n < 2)
return n;
if (map.containsKey(n))
return map.get(n);
int res = (fib(n-1,map)+fib(n-2,map))%1000000007;
map.put(n,res);
return map.get(n);
}
}
提交结果:
在leetcode题解中看到一位答主的代码如下所示:
int constant = 1000000007;
public int fib(int n) {
return fib(n, new HashMap());
}
public int fib(int n, Map<Integer, Integer> map) {
if (n < 2)
return n;
if (map.containsKey(n))
return map.get(n);
int first = fib(n - 1, map) % constant;
map.put(n - 1, first);
int second = fib(n - 2, map) % constant;
map.put(n - 2, second);
int res = (first + second) % constant;
map.put(n, res);
return res;
}
作者:sdwwld
链接:https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/solution/di-gui-he-fei-di-gui-liang-chong-fang-shi-du-ji-ba/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
他将结果拆成了first和last两部分,分别放入map中,其实不用这么拆,因为在执行int res = (fib(n-1,map)+fib(n-2,map))%1000000007;
和map.put(n,res);
的时候,已经递归地将除了0和1外的中间重复的数放入map中了。
2.2 动态规划法
根据昨天博客中的思想,我写出如下代码,分析过程见注释:
/**
* @author: szz
* @date: Created in 2021/1/31
* @description: 动态规划法求解斐波那契数列
* @version: 1.0
*/
public class Solution2 {
/**
* 分析一波:
* 1. 确定状态:
* 1.1 最后一步 f[n] = f[n-1]+f[n-2]
* 1.2 化为子问题 f[n-1] = f[n-2]+f[n-3]
* 2. 转移方程:
* f[n] = f[n-1]+f[n-2]
* 3. 初始条件:f[0] = 0 ,f[1] = 1
*
* 4.计算顺序,从头开始
*
*
*/
public int fib(int n) {
if (n == 0)
return 0;
if (n==1)
return 1;
int[] f = new int[n+1];
// 初始条件与边界条件
f[0] = 0;
f[1] = 1;
for (int i = 2; i < n+1; i++) {
f[i] = f[i-1] + f[i-2]; //转移方程
f[i] = f[i]%1000000007;
}
return f[n];
}
}
提交结果:
可以看出内存消耗太高了,因为为了求取最后一次的数,我单独创立了一个数组,存储斐波那契数列,这非常耗内存,所以对代码做出如下优化:
class Solution {
public int fib(int n) {
int a = 0, b = 1, sum;
for(int i = 0; i < n; i++){
sum = (a + b) % 1000000007;
a = b;
b = sum;
}
return a;
}
}
提交结果:
3. 青蛙跳台阶问题
3.1 问题描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
- 注:答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:2
示例 2:
输入:n = 7
输出:21
示例 3:
输入:n = 0
输出:1
3.2 问题分析
需要用动态规划做
- 确定状态
设跳上 n级台阶有 f(n)种跳法。在所有跳法中,青蛙的最后一步只有两种情况: 跳上 1 级或 2 级台阶。
- 当为 1 级台阶: 剩 n-1 个台阶,此情况共有 f(n-1) 种跳法;
- 当为 2 级台阶: 剩 n-2 个台阶,此情况共有 f(n-2) 种跳法。
- 转移方程
由1中分析,可得f(n) 为两种情况之和,即 f(n)=f(n-1)+f(n-2) ,该递推性质为斐波那契数列性质,所以本题可转化为求斐波那契数列第 n 项的值,与上一个问题一样,不同点在于起始点不同。 - 边界条件
- f ( 0 ) = 1 , f ( 1 ) = 1 f(0)=1,f(1)=1 f(0)=1,f(1)=1
- 确定顺序
肯定是从低到高跳,依次确定跳到第0,1,2,3…阶台阶的方法。
3.3 代码实现
package question10_2;
/**
* @author: szz
* @date: Created in 2021/2/1
* @description: 青蛙跳台阶问题
* @version: 1.0
*/
public class Solution {
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;
}
}