【LeetCode】509.斐波那契數
阿新 • • 發佈:2021-08-13
斐波那契數,通常用\(F(n)\) 表示,形成的序列稱為 斐波那契數列 。該數列由0 和 1 開始,後面的每一項數字都是前面兩項數字的和。也就是:
\(F(0) = 0,F(1)= 1\)
\(F(n) = F(n - 1) + F(n - 2)\),其中 \(n > 1\)
給你 \(n\) ,請計算 \(F(n)\) 。
示例 :
輸入:2
輸出:1
解釋:F(2) = F(1) + F(0) = 1 + 0 = 1
演算法1(暴力遞迴)
時間複雜度:\(O(2^n)\)
遞迴演算法的時間複雜度: 子問題個數 * 解決一個子問題需要的時間。
本題中子問題個數即為遞迴樹的節點個數,\(O(2^n)\)
class Solution {
public:
int fib(int n) {
if (n == 0) return 0;
if (n == 1 || n == 2) return 1;
return fib(n - 1) + fib(n - 2);
}
};
演算法2(帶備忘錄的遞迴)
時間複雜度:\(O(n)\)
因為該演算法沒有冗餘的計算過程,所以子問題個數為n(每個數計算一次),即\(O(n)\),解決每個子問題需要的時間為\(O(1)\)
由於演算法1中進行了多次的重複計算,例如在計算
f(20)
時需要計算f(19)
和f(18)
,在計算f(19)
時需要計算f(18)
和f(17)
,f(18)
被計算了兩次,為了避免這些重複計算,我們可以使用一個備忘錄(一般是陣列)記錄每次計算的結果,之後需要用到的時候直接從備忘錄中查詢。實際上,帶備忘錄的遞迴演算法,把一棵存在巨量冗餘的遞迴樹通過剪枝,改造成了一幅不存在冗餘的遞迴圖,極大減少了子問題(即遞迴圖中節點)的個數。
class Solution { public: int fib(int n) { int memo[n + 1]; for (int i = 0; i < n + 1; i ++ ) memo[i] = 0; return helper(memo, n); } int helper(int memo[], int n) { if (n == 0) return 0; if (n == 1) return 1; if (memo[n] != 0) return memo[n]; memo[n] = helper(memo, n - 1) + helper(memo, n - 2); return memo[n]; } };
演算法3(DP陣列的迭代)
時間複雜度:\(O(n)\)
演算法2帶備忘錄的遞迴求解方法是自頂向下逐層分解,分解到f(1)
和f(2)
這兩個base case就開始逐層返回答案。我們也可以通過自底向上逐層推出答案。
class Solution {
public:
int fib(int n) {
if (n == 0) return 0;
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];
}
};
演算法4(DP陣列迭代優化)
時間複雜度:\(O(n)\)
由於每一次計算時只需用到前兩次的結果,所以我們不需要儲存長度為n的dp表,只需要儲存前兩次的計算結果。
class Solution {
public:
int fib(int n) {
if (n == 0) return 0;
if (n == 2 || n == 1) return 1;
int pre = 0, cur = 1;
int res = 0;
for (int i = 2; i <= n; i ++ ) {
res = pre + cur;
pre = cur, cur = res;
}
return res;
}
};