上一篇文章01我们讲了解决动态规划问题的基本步骤,下面让我们开始进入实战!我们每一道题都来以解决动态规划问题的基本步骤来解题,我们先从简单题入手。话不多说Lis't GO!
斐波那契数
力扣:(509. 斐波那契数 - 力扣(LeetCode))
斐波那契数,通常⽤ F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后⾯的每⼀项数字都是前⾯两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给你n ,请计算 F(n) 。
示例 1:
输入:n = 2;
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1
示例 2:
输入:n = 3;
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2
示例 3:
输入:n = 4;
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3
看到这一题肯定很多朋友会说 : 这道题⽬简单,不需要做什么分析,直接顺⼿⼀写就过了。其实前面的基础部分题目可能都会比较简单,主要是为了通过这些题⽬让⼤家可以初步认识到,按照动规五部曲是如何解题的。对于动规,如果没有⽅法论的话,可能简单题⽬可以顺⼿⼀写就过,难⼀点就不知道如何下⼿了。
动规五部曲:
1. 确定dp数组以及下标的含义
要⽤⼀个⼀维dp数组来保存递推的结果。dp[i]的定义为:第i个数的斐波那契数值是dp[i]
2. 确定递推公式
为什么这是⼀道⾮常简单的⼊⻔题⽬呢?
因为题⽬已经把递推公式直接给我们了:状态转移⽅程 dp[i] = dp[i - 1] + dp[i - 2];
3. dp数组如何初始化
题⽬中把如何初始化也给我们了: dp[0] = 0; dp[1] = 1;
4. 确定遍历顺序
从递归公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,dp[i]是依赖 dp[i - 1] 和 dp[i - 2],那么遍历的顺序⼀定是从前到后遍历的。
5. 举例推导dp数组
按照这个递推公式dp[i] = dp[i - 1] + dp[i - 2],我们来推导⼀下,当N为10的时候,dp数组应该是如下的
数列:
0 1 1 2 3 5 8 13 21 34 55
如果代码写出来,发现结果不对,就把dp数组打印出来看看和我们推导的数列是不是⼀致的。
以上我们⽤动规的⽅法分析完了,Java代码如下:
class Solution {
public int fib(int n) {
int[] arr = new int[n + 1];
for(int i = 0; i <= n; i++){
if(i == 0 || i == 1)
arr[i] = i;
else
arr[i] = arr[i - 1] + arr[i - 2];
}
return arr[n];
}
}
还可以发现,我们只需要维护两个数值就可以了,不需要记录整个序列。因为当前所求只与前两个值有关。
class Solution {
public int fib(int n) {
if(n <= 1)
return n;
int t1 = 0;
int t2 = 1;
for(int i = 2; i <= n; i++){
int sum = t1 + t2;
t1 = t2;
t2 = sum;
}
return t2;
}
}
当然本体也可以用递归来解答
class Solution {
public int fib(int n) {
if(n <= 1)
return n;
return fib(n - 1) + fib(n - 2);
}
}
只要比较一下两种方法的运行所用时间,动态规划的优势就可以很明显的体现出来的:
第一个是递归所需的时间
注:本文参考微信公众号:代码随想录