题目
泰波那契序列 Tn 定义如下:
T0 = 0, T1 = 1, T2 = 1, 且在 n >= 0 的条件下 Tn+3 = Tn + Tn+1 + Tn+2。
给你整数 n,请返回第 n 个泰波那契数 Tn 的值。
示例 1:
输入:n = 4
输出:4
解释:
T_3 = 0 + 1 + 1 = 2
T_4 = 1 + 1 + 2 = 4
示例 2:
输入:n = 25
输出:1389537
提示:
- 0 <= n <= 37
- 答案保证是一个 32 位整数,即 answer <= 2^31 - 1。
思路
由题目所给:泰波那契序列 Tn 的定义: T0 = 0, T1 = 1, T2 = 1, 且在 n >= 0 的条件下 Tn+3 = Tn + Tn+1 + Tn+2。
可得Tn = Tn-3 + Tn-2 + Tn-1。
- T0 = 0
- T1 = 1
- T2 = 1
- T3 = 2
- T4 = 4
- T5 = 7
......
做题流程:
- 动态规划一般会先定义dp表:一维数组/二维数组。
- 想办法将dp数组填满。
- 填满后dp数组中的某一个值就是最终结果。
step1:状态表示(确定dp数组以及下标含义)
①是什么?
状态表示就是dp表中某一个值所表示的含义。
②怎么来?
- 题目要求。
- 经验(大量做题)+题目要求。
- 在分析问题的过程中发现重复子问题,把这个重复子问题抽象成一个状态表示。
是最重要的一步!
本题中直接根据题目要求:
- 让dp[0]表示第0个泰波那契数的值
- 让dp[1]表示第1个泰波那契数的值
- 让dp[2]表示第2个泰波那契数的值
- ......
- 让dp[i]表示第i个泰波那契数的值
- 最后返回dp[n],即为第n个泰波那契数的值
step2:状态转移方程(确定递推公式/状态转移公式)
①是什么?
状态转移方程就是dp[i]等于什么?
②怎么来?
就题分析~
是最难的一步!
本题中直接得出:dp[i] = dp[i -1] + dp[i -2] + dp[i -3]
step3:初始化(dp数组如何初始化)
①含义?
初始化就是保证填表的时候不越界。
②怎么填表?
就是根据状态转移方程来进行填表!
若是:
- dp[0] = dp[-1] + dp[-2] + dp[-3]
- dp[1] = dp[0] + dp[-1] + dp[-2]
- dp[2] = dp[1] + dp[0] + dp[-1]
其中dp[-1]、dp[-2]、dp[-3]越界,无法访问。
so:dp数组的前3个位置会越界访问,那么就需要将前3个位置进行初始化。那么之后的位置就不会出现越界访问问题了。
本题中直接得出:dp[0] = 0、dp[1] = 1、dp[2] = 1。
step4:填表顺序(确定遍历顺序)
确定填表顺序是为了在填写当前状态的时候,所需要的状态已经计算过了。
本题中的填表顺序是:从左到右。
step5:返回值(举例推导dp数组)
题目要求+状态表示
本题中返回值是:dp[n]。
代码1
class Solution {
public int tribonacci(int n) {
//处理边界情况
if(n == 0) {
return 0;
}
if(n == 1 || n ==2) {
return 1;
}
//1.创建dp表
int[] dp = new int[n + 1];
//2.初始化
dp[0] = 0;
dp[1] = dp[2] = 1;
//3.填表
for(int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2] + dp[i -3];
}
//4.返回值
return dp[n];
}
}
- 时间复杂度:O(n)
- 空间复杂度:O(n)
空间优化
关于动态规划问题的空间优化,一般都是使用滚动数组。
- 在依次向后求dp[i]时,前面的一些不用的状态可以舍弃,仅仅用中间有效的若干的状态即可。
- 这样的情况都可以用滚动数组来做优化!
- 设置对应数量的变量,利用这些变量求出下一个值,依次将这些变量向后滚动一位(赋值操作),继续求下一个值,以此类推。
PS:赋值操作有两种实现顺序:
- 从前向后(正确)
- 从后向前(错误,之前的值会被覆盖)
优化结果:
- 之前是O(n ^ 2) -> 优化后是O(n)
- 之前是O(n) -> 优化后是O(1)(仅用几个有限的变量即可)
本题中:
- 在求dp[4]时只用到dp[1]、dp[2]、dp[3],没有用到dp[0]。
- 在求dp[5]时只用到dp[2]、dp[3]、dp[4],没有用到dp[0]、dp[1]。
- ......
- 在求dp[i]时只用到dp[i - 1]、dp[i - 2]、dp[i - 3]。
- 那么只需要设置a,b,c三个变量即可,让这三个变量开始时a = dp[0] = 0,b = dp[1] = 1,c =dp[2] = 1。
- 用这三个变量更新完变量d = dp[3]后,将a、b、c、d四个变量依次向右滚动一位。
- 赋值操作顺序:
- 从左向右:a = b,b = c,c = d(正确)。
- 从右向左:c = d,b = c,a = b(错误,之前的值会被覆盖)。
- 同理可以利用a、b、c求得d,即dp[4]、dp[5]......
代码2
class Solution {
public int tribonacci(int n) {
//处理边界情况
if(n == 0) {
return 0;
}
if(n == 1 || n ==2) {
return 1;
}
int a = 0, b = 1, c = 1, d = 0;
for(int i = 3; i <= n; i++) {
d = a + b + c;
//滚动操作
a = b;
b = c;
c = d;
}
return d;
}
}