题目:
斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n ,请计算 F(n) 。
暴力破解
斐波那契数列的数学形式就是递归的,写成代码为:
class Solution {
public int fib(int n) {
if(n == 0) return 0;
if(n == 1 || n ==2) return 1;
return fib(n - 1) + fib(n - 2);
}
}
虽然暴力破解可以解决此问题,但是在计算中会产生大量冗余,以fib(20)为例:
其中fib(18)、fib(17)进行多次复用,计算量巨大。
以时间复杂度说明,子问题个数为树中节点的组数为O(),一个子问题解决时间为fib(n-1)+fib(n-2)即为O(1),两者相乘时间复杂度为O()。
带"备忘录"的递归解法
既然重复的原因是因为重复计算,则可以创建一个备忘录,将每次计算后的数值记录到备忘录中,在子问题计算前查看备忘录是否含有值,可以减少计算。
class Solution {
public int fib(int n) {
int[] memo = new int[n+1];
return helper(memo, n);
}
public int helper(int[] memo, int n){
if(n == 0 || n == 1) return n;
if(memo[n] != 0) return memo[n];
memo[n] = helper(memo, n - 1) + helper(memo, n - 2);
return memo[n];
}
}
此时计算过的fib(n)存入至memo数组中不必在进行重复计算,减少了子问题个数。子问题的个数减少至n,时间复杂度为O(N)。这是自顶向下的递归做法。
dp数组的递推解法
dp数组的递归方法为自底向上的解法,从最底下、最简单、问题规模最小向上推,直到达到所求解。
class Solution {
public int fib(int n) {
if(n == 0) return 0;
int[] dp = new int[n + 1];
dp[0] = 0;
dp[1] = 1;
for(int i = 2; i <= n; i++){
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}
只需要计算dp[n]数组的子问题,即可遍历完成。 但其实我们每次只需要前两个dp数组的值,dp数组过于冗余,因此可以简化为
class Solution {
public int fib(int n) {
if(n == 0 || n == 1) return n;
int dp_i_1 = 1, dp_i_2 = 0;
for(int i = 2; i <= n; i++){
int dp_i = dp_i_1 + dp_i_2;
dp_i_2 = dp_i_1;
dp_i_1 = dp_i;
}
return dp_i_1;
}
}
将空间复杂度降低为O(1)。
解题思路参考labuladong的算法笔记1.3.1章。