leetcode-master/problems/0509.斐波那契数.md

444 lines
10 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/>
</a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 509. 斐波那契数
[力扣题目链接](https://leetcode.cn/problems/fibonacci-number/)
斐波那契数通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给你n ,请计算 F(n) 。
示例 1
* 输入2
* 输出1
* 解释F(2) = F(1) + F(0) = 1 + 0 = 1
示例 2
* 输入3
* 输出2
* 解释F(3) = F(2) + F(1) = 1 + 1 = 2
示例 3
* 输入4
* 输出3
* 解释F(4) = F(3) + F(2) = 2 + 1 = 3
提示:
* 0 <= n <= 30
# 视频讲解
**《代码随想录》算法视频公开课:[手把手带你入门动态规划 | leetcode509.斐波那契数](https://www.bilibili.com/video/BV1f5411K7mo),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 思路
斐波那契数列大家应该非常熟悉不过了,非常适合作为动规第一道题目来练练手。
因为这道题目比较简单,可能一些同学并不需要做什么分析,直接顺手一写就过了。
**但「代码随想录」的风格是:简单题目是用来加深对解题方法论的理解的**
通过这道题目让大家可以初步认识到,按照动规五部曲是如何解题的。
对于动规,如果没有方法论的话,可能简单题目可以顺手一写就过,难一点就不知道如何下手了。
所以我总结的动规五部曲,是要用来贯穿整个动态规划系列的,就像之前讲过[二叉树系列的递归三部曲](https://www.programmercarl.com/二叉树的递归遍历.html)[回溯法系列的回溯三部曲](https://programmercarl.com/回溯算法理论基础.html)一样。后面慢慢大家就会体会到,动规五部曲方法的重要性。
### 动态规划
动规五部曲:
这里我们要用一个一维dp数组来保存递归的结果
1. 确定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数组打印出来看看和我们推导的数列是不是一致的。
以上我们用动规的方法分析完了C++代码如下:
```CPP
class Solution {
public:
int fib(int N) {
if (N <= 1) return N;
vector<int> dp(N + 1);
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= N; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[N];
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
当然可以发现,我们只需要维护两个数值就可以了,不需要记录整个序列。
代码如下:
```CPP
class Solution {
public:
int fib(int N) {
if (N <= 1) return N;
int dp[2];
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= N; i++) {
int sum = dp[0] + dp[1];
dp[0] = dp[1];
dp[1] = sum;
}
return dp[1];
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
### 递归解法
本题还可以使用递归解法来做
代码如下:
```CPP
class Solution {
public:
int fib(int N) {
if (N < 2) return N;
return fib(N - 1) + fib(N - 2);
}
};
```
* 时间复杂度O(2^n)
* 空间复杂度O(n),算上了编程语言中实现递归的系统栈所占空间
这个递归的时间复杂度大家画一下树形图就知道了,如果不清晰的同学,可以看这篇:[通过一道面试题目,讲一讲递归算法的时间复杂度!](https://programmercarl.com/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.html)
## 总结
斐波那契数列这道题目是非常基础的题目,我在后面的动态规划的讲解中将会多次提到斐波那契数列!
这里我严格按照[关于动态规划,你该了解这些!](https://programmercarl.com/动态规划理论基础.html)中的动规五部曲来分析了这道题目,一些分析步骤可能同学感觉没有必要搞的这么复杂,代码其实上来就可以撸出来。
但我还是强调一下,简单题是用来掌握方法论的,动规五部曲将在接下来的动态规划讲解中发挥重要作用,敬请期待!
就酱,循序渐进学算法,认准「代码随想录」!
## 其他语言版本
### Java
```Java
class Solution {
public int fib(int n) {
if (n < 2) return n;
int a = 0, b = 1, c = 0;
for (int i = 1; i < n; i++) {
c = a + b;
a = b;
b = c;
}
return c;
}
}
```
```java
//非压缩状态的版本
class Solution {
public int fib(int n) {
if (n <= 1) return n;
int[] dp = new int[n + 1];
dp[0] = 0;
dp[1] = 1;
for (int index = 2; index <= n; index++){
dp[index] = dp[index - 1] + dp[index - 2];
}
return dp[n];
}
}
```
### Python
```python
class Solution:
def fib(self, n: int) -> int:
if n < 2:
return n
a, b, c = 0, 1, 0
for i in range(1, n):
c = a + b
a, b = b, c
return c
# 动态规划 (注释版。无修饰)
class Solution:
def fib(self, n: int) -> int:
# 排除 Corner Case
if n == 0:
return 0
# 创建 dp table
dp = [0] * (n + 1)
# 初始化 dp 数组
dp[0] = 0
dp[1] = 1
# 遍历顺序: 由前向后。因为后面要用到前面的状态
for i in range(2, n + 1):
# 确定递归公式/状态转移公式
dp[i] = dp[i - 1] + dp[i - 2]
# 返回答案
return dp[n]
# 递归实现
class Solution:
def fib(self, n: int) -> int:
if n < 2:
return n
return self.fib(n - 1) + self.fib(n - 2)
```
### Go
```Go
func fib(n int) int {
if n < 2 {
return n
}
a, b, c := 0, 1, 0
for i := 1; i < n; i++ {
c = a + b
a, b = b, c
}
return c
}
```
### Javascript
解法一
```Javascript
var fib = function(n) {
let dp = [0, 1]
for(let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2]
}
console.log(dp)
return dp[n]
};
```
解法二时间复杂度O(N)空间复杂度O(1)
```Javascript
var fib = function(n) {
// 动规状态转移中当前结果只依赖前两个元素的结果所以只要两个变量代替dp数组记录状态过程。将空间复杂度降到O(1)
let pre1 = 1
let pre2 = 0
let temp
if (n === 0) return 0
if (n === 1) return 1
for(let i = 2; i <= n; i++) {
temp = pre1
pre1 = pre1 + pre2
pre2 = temp
}
return pre1
};
```
### TypeScript
```typescript
function fib(n: number): number {
/**
dp[i]: 第i个斐波那契数
dp[0]: 0;
dp[1]1
...
dp[i] = dp[i - 1] + dp[i - 2];
*/
const dp: number[] = [];
dp[0] = 0;
dp[1] = 1;
for (let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
};
```
### C
动态规划:
```c
int fib(int n){
//当n <= 1时返回n
if(n <= 1)
return n;
//动态开辟一个int数组大小为n+1
int *dp = (int *)malloc(sizeof(int) * (n + 1));
//设置0号位为01号为为1
dp[0] = 0;
dp[1] = 1;
//从前向后遍历数组(i=2; i <= n; ++i),下标为n时的元素为dp[i-1] + dp[i-2]
int i;
for(i = 2; i <= n; ++i) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
```
递归实现
```c
int fib(int n){
//若n小于等于1返回n
if(n <= 1)
return n;
//否则返回fib(n-1) + fib(n-2)
return fib(n-1) + fib(n-2);
}
```
### Rust
动态规划:
```Rust
pub fn fib(n: i32) -> i32 {
let n = n as usize;
let mut dp = vec![0; 31];
dp[1] = 1;
for i in 2..=n {
dp[i] = dp[i - 1] + dp[i - 2];
}
dp[n]
}
```
递归实现
```Rust
pub fn fib(n: i32) -> i32 {
//若n小于等于1返回n
f n <= 1 {
return n;
}
//否则返回fib(n-1) + fib(n-2)
return fib(n - 1) + fib(n - 2);
}
```
### Scala
动态规划:
```scala
object Solution {
def fib(n: Int): Int = {
if (n <= 1) return n
var dp = new Array[Int](n + 1)
dp(1) = 1
for (i <- 2 to n) {
dp(i) = dp(i - 1) + dp(i - 2)
}
dp(n)
}
}
```
递归:
```scala
object Solution {
def fib(n: Int): Int = {
if (n <= 1) return n
fib(n - 1) + fib(n - 2)
}
}
```
### C#
动态规划:
```c#
public class Solution
{
public int Fib(int n)
{
if(n<2) return n;
int[] dp = new int[2] { 0, 1 };
for (int i = 2; i <= n; i++)
{
int temp = dp[0] + dp[1];
dp[0] = dp[1];
dp[1] = temp;
}
return dp[1];
}
}
```
递归:
```c#
public class Solution
{
public int Fib(int n)
{
if(n<2)
return n;
return Fib(n-1)+Fib(n-2);
}
}
```
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a>