0x01 书籍和算法
文明的传播,科技的兴起,工业***萌芽,都可以归功于印刷术的出现,而另一些人认为这一切的关键是算法的发展。
0x02 大O符号
大O符号(Big O notation), 又称渐进符号,是用于描述函数的渐近行为的数学符号。它是指用另一个(通常更简单的)函数来描述一个函数数量级的渐进上界。
0x03 斐波那契数列
数列公式
F(n) = F(n-1)+F(n-2) if n> 1
F(n) = 1 if n=1
F(n) = 0 if n=0
n种解法
为了避免考虑整数溢出问题,我们将结果%1000000007。
递归
递归计算的节点个数是 O(2^n) 的级别的,存在大量重复计算。
时间复杂度是 O(2^n),一秒内大约能算到第三四十项。
const int MOD = 1000000007; int f(int n) { if (n <= 1) return 1; return (f(n - 1) + f(n - 2)) % MOD; }
记忆化搜索。
开一个大数组记录中间结果,如果一个状态被计算过,则直接查表,否则再递归计算。
总共有 n 个状态,计算每个状态的复杂度是 O(1),所以时间复杂度是 O(n)。
一秒内算 n=10^7 毫无压力,但由于是递归计算,递归层数太多会爆栈,大约只能算到 n=10^5 级别。
const int N = 100000, MOD = 1000000007; int a[N]; int f2(int n) { if (a[n]) return a[n]; if (n <= 1) return 1; a[n] = f2(n - 1) + f2(n - 2); a[n] %= MOD; return a[n]; }
递推
开一个大数组,记录每个数的值。用循环递推计算。
总共计算 n 个状态,所以时间复杂度是 O(n)。
但需要开一个长度是 n 的数组,内存将成为瓶颈,当 n=10^8 时,需要的内存是 4∗10^8x1024×1024≈381MB。
分子中乘4是因为C++中 int 类型占4字节。
const int N = 100000000, MOD = 1000000007; int f3(int n) { a[0] = a[1] = 1; for (int i = 2; i <= n; i ++ ) { a[i] = a[i - 1] + a[i - 2]; a[i] %= MOD; } return a[n]; }
递推+滚动变量
仔细观察我们会发现,递推时我们只需要记录前两项的值即可,没有必要记录所有值,所以我们可以用滚动变量递推。
时间复杂度还是 O(n),但空间复杂度变成了 O(1)。
const int MOD = 1000000007; int f4(int n) { int x, y, z; x = y = 1; for (int i = 2; i <= n; i ++ ) { z = (x + y) % MOD; x = y; y = z; } return z; }