|
| 1 | +### 题目描述 |
| 2 | + |
| 3 | +这是 LeetCode 上的 **[剑指 Offer 10- I. 斐波那契数列](https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/solution/gong-shui-san-xie-yi-ti-si-jie-dong-tai-9zip0/)** ,难度为 **简单**。 |
| 4 | + |
| 5 | +Tag : 「动态规划」、「线性 DP」、「记忆化搜索」、「打表」、「矩阵快速幂」 |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | +写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下: |
| 10 | + |
| 11 | +* F(0) = 0, F(1) = 1 |
| 12 | +* F(N) = F(N - 1) + F(N - 2), 其中 N > 1. |
| 13 | + |
| 14 | +斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。 |
| 15 | + |
| 16 | +答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。 |
| 17 | + |
| 18 | +示例 1: |
| 19 | +``` |
| 20 | +输入:n = 2 |
| 21 | + |
| 22 | +输出:1 |
| 23 | +``` |
| 24 | +示例 2: |
| 25 | +``` |
| 26 | +输入:n = 5 |
| 27 | + |
| 28 | +输出:5 |
| 29 | +``` |
| 30 | + |
| 31 | +提示: |
| 32 | +* 0 <= n <= 100 |
| 33 | + |
| 34 | +--- |
| 35 | + |
| 36 | +### 递推实现动态规划 |
| 37 | + |
| 38 | +既然转移方程都给出了,直接根据转移方程从头到尾递递推一遍即可。 |
| 39 | + |
| 40 | +代码: |
| 41 | +```Java |
| 42 | +class Solution { |
| 43 | + int mod = (int)1e9+7; |
| 44 | + public int fib(int n) { |
| 45 | + if (n <= 1) return n; |
| 46 | + int a = 0, b = 1; |
| 47 | + for (int i = 2; i <= n; i++) { |
| 48 | + int c = a + b; |
| 49 | + c %= mod; |
| 50 | + a = b; |
| 51 | + b = c; |
| 52 | + } |
| 53 | + return b; |
| 54 | + } |
| 55 | +} |
| 56 | +``` |
| 57 | +* 时间复杂度:$O(n)$ |
| 58 | +* 空间复杂度:$O(1)$ |
| 59 | + |
| 60 | +--- |
| 61 | + |
| 62 | +### 递归实现动态规划 |
| 63 | + |
| 64 | +能以「递推」形式实现动态规划,自然也能以「递归」的形式实现。 |
| 65 | + |
| 66 | +为防止重复计算,我们需要加入「记忆化搜索」功能,同时利用某个值 $x$ 在不同的样例之间可能会作为"中间结果"被重复计算,并且计算结果 $fib(x)$ 固定,我们可以使用 `static` 修饰缓存器,以实现计算过的结果在所有测试样例中共享。 |
| 67 | + |
| 68 | +代码: |
| 69 | +```Java |
| 70 | +class Solution { |
| 71 | + static int mod = (int)1e9+7; |
| 72 | + static int N = 110; |
| 73 | + static int[] cache = new int[N]; |
| 74 | + public int fib(int n) { |
| 75 | + if (n <= 1) return n; |
| 76 | + if (cache[n] != 0) return cache[n]; |
| 77 | + cache[n] = fib(n - 1) + fib(n - 2); |
| 78 | + cache[n] %= mod; |
| 79 | + return cache[n]; |
| 80 | + } |
| 81 | +} |
| 82 | +``` |
| 83 | +* 时间复杂度:$O(n)$ |
| 84 | +* 空间复杂度:$O(1)$ |
| 85 | + |
| 86 | +--- |
| 87 | + |
| 88 | +### 打表 |
| 89 | + |
| 90 | +经过「解法二」,我们进一步发现,可以利用数据范围只有 100ドル$ 进行打表预处理,然后直接返回。 |
| 91 | + |
| 92 | +代码: |
| 93 | +```Java |
| 94 | +class Solution { |
| 95 | + static int mod = (int)1e9+7; |
| 96 | + static int N = 110; |
| 97 | + static int[] cache = new int[N]; |
| 98 | + static { |
| 99 | + cache[1] = 1; |
| 100 | + for (int i = 2; i < N; i++) { |
| 101 | + cache[i] = cache[i - 1] + cache[i - 2]; |
| 102 | + cache[i] %= mod; |
| 103 | + } |
| 104 | + } |
| 105 | + public int fib(int n) { |
| 106 | + return cache[n]; |
| 107 | + } |
| 108 | +} |
| 109 | +``` |
| 110 | +* 时间复杂度:将打表逻辑放到本地执行,复杂度为 $O(1)$;否则为 $O(C),ドル$C$ 为常量,固定为 110ドル$ |
| 111 | +* 空间复杂度:$O(C)$ |
| 112 | + |
| 113 | +--- |
| 114 | + |
| 115 | +### 矩阵快速幂 |
| 116 | + |
| 117 | +**对于数列递推问题,可以使用矩阵快速幂进行加速,最完整的介绍在 [这里](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247488198&idx=1&sn=8272ca6b0ef6530413da4a270abb68bc&chksm=fd9cb9d9caeb30cf6c2defab0f5204adc158969d64418916e306f6bf50ae0c38518d4e4ba146&token=1067450240&lang=zh_CN#rd) 讲过。** |
| 118 | + |
| 119 | +对于本题,某个 $f(n)$ 依赖于 $f(n - 1)$ 和 $f(n - 2),ドル将其依赖的状态存成列向量: |
| 120 | + |
| 121 | +$$ |
| 122 | +\begin{bmatrix} |
| 123 | +f(n - 1)\\ |
| 124 | +f(n - 2) |
| 125 | +\end{bmatrix} |
| 126 | +$$ |
| 127 | + |
| 128 | +目标值 $f(n)$ 所在矩阵为: |
| 129 | + |
| 130 | +$$ |
| 131 | +\begin{bmatrix} |
| 132 | +f(n)\\ |
| 133 | +f(n - 1) |
| 134 | +\end{bmatrix} |
| 135 | +$$ |
| 136 | + |
| 137 | +根据矩阵乘法,不难发现: |
| 138 | + |
| 139 | +$$ |
| 140 | +\begin{bmatrix} |
| 141 | +f(n)\\ |
| 142 | +f(n - 1) |
| 143 | +\end{bmatrix} |
| 144 | + |
| 145 | += |
| 146 | + |
| 147 | +\begin{bmatrix} |
| 148 | +1, 1\\ |
| 149 | +1, 0 |
| 150 | +\end{bmatrix} |
| 151 | + |
| 152 | +* |
| 153 | + |
| 154 | +\begin{bmatrix} |
| 155 | +f(n - 1)\\ |
| 156 | +f(n - 2) |
| 157 | +\end{bmatrix} |
| 158 | +$$ |
| 159 | + |
| 160 | +我们令: |
| 161 | + |
| 162 | +$$ |
| 163 | +mat = |
| 164 | +\begin{bmatrix} |
| 165 | +1, 1\\ |
| 166 | +1, 0 |
| 167 | +\end{bmatrix} |
| 168 | +$$ |
| 169 | + |
| 170 | +起始时,我们只有 $ |
| 171 | +\begin{bmatrix} |
| 172 | +f(1)\\ |
| 173 | +f(0) |
| 174 | +\end{bmatrix} |
| 175 | +,ドル根据递推式得: |
| 176 | + |
| 177 | +$$ |
| 178 | +\begin{bmatrix} |
| 179 | +f(n)\\ |
| 180 | +f(n - 1) |
| 181 | +\end{bmatrix} |
| 182 | + |
| 183 | += |
| 184 | + |
| 185 | +mat * mat * ... * mat * |
| 186 | + |
| 187 | +\begin{bmatrix} |
| 188 | +f(1)\\ |
| 189 | +f(0) |
| 190 | +\end{bmatrix} |
| 191 | +$$ |
| 192 | + |
| 193 | +再根据矩阵乘法具有「结合律」,最终可得: |
| 194 | + |
| 195 | +$$ |
| 196 | +\begin{bmatrix} |
| 197 | +f(n)\\ |
| 198 | +f(n - 1) |
| 199 | +\end{bmatrix} |
| 200 | + |
| 201 | += |
| 202 | + |
| 203 | +mat^{n - 1} |
| 204 | + |
| 205 | +* |
| 206 | + |
| 207 | +\begin{bmatrix} |
| 208 | +f(1)\\ |
| 209 | +f(0) |
| 210 | +\end{bmatrix} |
| 211 | +$$ |
| 212 | + |
| 213 | +计算 $mat^{n - 1}$ 可以套用「快速幂」进行求解。 |
| 214 | + |
| 215 | +代码: |
| 216 | +```Java |
| 217 | +class Solution { |
| 218 | + int mod = (int)1e9+7; |
| 219 | + long[][] mul(long[][] a, long[][] b) { |
| 220 | + int r = a.length, c = b[0].length, z = b.length; |
| 221 | + long[][] ans = new long[r][c]; |
| 222 | + for (int i = 0; i < r; i++) { |
| 223 | + for (int j = 0; j < c; j++) { |
| 224 | + for (int k = 0; k < z; k++) { |
| 225 | + ans[i][j] += a[i][k] * b[k][j]; |
| 226 | + ans[i][j] %= mod; |
| 227 | + } |
| 228 | + } |
| 229 | + } |
| 230 | + return ans; |
| 231 | + } |
| 232 | + public int fib(int n) { |
| 233 | + if (n <= 1) return n; |
| 234 | + long[][] mat = new long[][]{ |
| 235 | + {1, 1}, |
| 236 | + {1, 0} |
| 237 | + }; |
| 238 | + long[][] ans = new long[][]{ |
| 239 | + {1}, |
| 240 | + {0} |
| 241 | + }; |
| 242 | + int x = n - 1; |
| 243 | + while (x != 0) { |
| 244 | + if ((x & 1) != 0) ans = mul(mat, ans); |
| 245 | + mat = mul(mat, mat); |
| 246 | + x >>= 1; |
| 247 | + } |
| 248 | + return (int)(ans[0][0] % mod); |
| 249 | + } |
| 250 | +} |
| 251 | +``` |
| 252 | +* 时间复杂度:$O(\log{n})$ |
| 253 | +* 空间复杂度:$O(1)$ |
| 254 | + |
| 255 | +--- |
| 256 | + |
| 257 | +### 最后 |
| 258 | + |
| 259 | +这是我们「刷穿 LeetCode」系列文章的第 `剑指 Offer 10- I` 篇,系列开始于 2021年01月01日,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 |
| 260 | + |
| 261 | +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 |
| 262 | + |
| 263 | +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 |
| 264 | + |
| 265 | +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 |
| 266 | + |
0 commit comments