502. [数组][递归][动态规划]斐波那契数
502. 斐波那契数
方法一:动态规划(伪)之记忆化搜索

上图为未剪枝的递归树,如果使用常规递归,可以很直观的看到耗时的原因是重复计算。

上图为经过剪枝的递归树,可以看到它从一棵\(O(2^n)\)的递归树化简成了\(O(n)\)的递归序列。在算法实现中,将已经计算过的数值保存到了\(memo\)数组中,避免了重复计算。
// 执行耗时:0 ms,击败了100.00% 的Java用户
// 内存消耗:35 MB,击败了92.33% 的Java用户
class Solution {
public int fib(int N) {
if (N < 1) return 0;
int[] memo = new int[N+1];
return helper(memo, N);
}
public int helper(int[] memo, int N){
// base case
if (N == 1 || N == 2) return 1;
// already calculate
// pruning
if (memo[N] != 0) return memo[N];
memo[N] = helper(memo, N - 1) + helper(memo, N - 2);
return memo[N];
}
}
方法二:动态规划(伪)之dp 数组的迭代解法
受上一步记忆化搜索的启发,可以单独把\(memo\)拎出来,其实就是DP Table,在这张表上,可以实现自底向上的推算。
所谓状态转移,其实就是把 f(n) 想做一个状态 n,这个状态 n 是由状态 n - 1 和状态 n - 2 相加转移而来,这就叫状态转移,仅此而已。在这个问题中,其实我们发觉了这个问题结构的数学形式,简而言之,就是下面的递推式。
\[f(x)=
\begin{cases}
1, \text{ n=1, 2}\\
f(n-1) +f(n-2),& \text{n>2}
\end{cases}
\]
// 执行耗时:0 ms,击败了100.00% 的Java用户
// 内存消耗:34.9 MB,击败了97.36% 的Java用户
class Solution {
public int fib(int N) {
if (N < 1) return 0;
if (N == 1 || N == 2) return 1;
int[] dp = new int[N + 1];
// base case
dp[1] = dp[2] = 1;
for (int i = 3; i <= N; i++){
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[N];
}
}
方法三:动态规划(伪)之dp 状态压缩
我们不难发现,每次状态转移时,其实只需要DP Table中的一部分,从这个角度出发,我们可以从这个角度来压缩DP Table的空间复杂度,具体实现如下,即把二维的DP table压缩为一维,用三个变量来满足状态转移,此时的空间复杂度即为\(O(1)\),这个技巧就是所谓的状态压缩。
// 执行耗时:0 ms,击败了100.00% 的Java用户
// 内存消耗:35.2 MB,击败了86.92% 的Java用户
class Solution {
public int fib(int N) {
if (N < 1) return 0;
if (N == 1 || N == 2) return 1;
// base case
int prev = 1, curr = 1;
for (int i = 3; i <= N; i++){
int sum = prev + curr;
prev = curr;
curr = sum;
}
return curr;
}
}

浙公网安备 33010602011771号