题目
方法一-递归法
将
f
(
n
)
f(n)
f(n)的计算拆分成
f
(
n
−
1
)
f(n-1)
f(n−1)和
f
(
n
−
2
)
f(n-2)
f(n−2)两个子问题的计算,递归,以
f
(
0
)
f(0)
f(0)和
f
(
1
)
f(1)
f(1)为递归终止条件。
显然会进行大量重复计算,代码虽然简单但算法效率很低。
- 时间复杂度(二叉树的节点总数): O ( 2 n ) O(2^n) O(2n),n 等于二叉树的高度
- 空间复杂度: O ( n ) O(n) O(n),即树的高度
方法二-记忆化递归法
在递归法基础上用一个长度为 n 的数组存储
f
(
0
)
f(0)
f(0)至
f
(
n
)
f(n)
f(n)的值,避免了重复的递归计算。
但存储需要额外
O
(
n
)
O(n)
O(n)的空间
方法三-动态规划
分治方法将问题划分为互不相交的子问题,递归求解子问题,再将它们的解组合起来求出原问题的解。在子问题重叠的情况下,即不同的子问题具有公共的子子问题时,分治算法会反复求解公共子子问题,而动态规划算法对每个子问题只求解一次。
由于
f
(
n
)
f(n)
f(n)只与
f
(
n
−
1
)
f(n-1)
f(n−1)和
f
(
n
−
2
)
f(n-2)
f(n−2)有关,因此只需要三个变量
s
u
m
sum
sum,
a
a
a,
b
b
b分别表示
f
(
n
)
f(n)
f(n)、
f
(
n
−
1
)
f(n-1)
f(n−1)和
f
(
n
−
2
)
f(n-2)
f(n−2),将
s
u
m
sum
sum作为辅助变量使
a
a
a,
b
b
b交替前进。
关于为什么要取模1e9+7(有时也会改为要求取模1e9 + 9和998244353):
都是小于
2
30
2^{30}
230的质数,因此取模后的两数相加在 int 范围内不会溢出,即
a
+
b
<
2
31
a+b<2^{31}
a+b<231;取模后的两数相乘在long long 范围内不会溢出,即
a
b
<
2
60
ab<2^{60}
ab<260。另外,对质数取模能尽可能避免模数相同的数之间存在公因数,从而减少冲突,并且方便进行除法运算(因为除了0都存在逆元)。
逆元的作用
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;
}
}
- 时间复杂度: O ( n ) O(n) O(n),循环 n 次,每轮循环内计算操作需要 O ( 1 ) O(1) O(1)
- 空间复杂度: O ( 1 ) O(1) O(1),3个变量使用常数大小的额外空间