在遞歸中由於需要調用自身可能會導致一些重複的計算,函數的調用不僅需要時間,而且也會暫用棧上的空間。有時候如果調用的深度太深,還會導致無限循環的問題(實際上是深度太深,要長時間的調用,導致無法在預期的時間內返回)。要解決這個問題,可以使用“記憶化”的方式,將需要的數據緩存,以減少重複的計算。
斐波那契數
鏈接:斐波那契數
描述:
斐波那契數,通常用 F(n) 表示,形成的序列稱爲斐波那契數列。該數列由 0 和 1 開始,後面的每一項數字都是前面兩項數字的和。也就是:F(0) = 0, F(1) = 1。F(N) = F(N - 1) + F(N - 2), 其中 N > 1.給定 N,計算 F(N)。假設0 ≤ N ≤ 30。
一般解法:
class Solution {
public:
int fib(int N) {
if (N == 0 || N == 1) return N;
return fib(N - 1) + fib(N - 2);
}
};
帶緩存的解法:
由於題目中N的長度是有限的,且每個斐波那契數都只佔用一個INT的長度,因此最多只需要申請一個INT[31]長度的數組即可以緩存所有數據,代碼如下:
class Solution {
public:
int fib(int N) {
static int sCaches[31] = {0};
if (sCaches[N] != 0 || N == 0) return sCaches[N];
if(N == 1) {
sCaches[1] = 1;
return 1;
}
sCaches[N] = fib(N - 1) + fib(N - 2);
return sCaches[N];
}
};
爬樓梯
鏈接:爬樓梯
描述:
假設你正在爬樓梯。需要 n 階你才能到達樓頂。每次你可以爬 1 或 2 個臺階。你有多少種不同的方法可以爬到樓頂呢?注意:給定 n 是一個正整數。
分析:
假設有n(n >= 2)階臺階總共有f(n)總方法,你可以選擇先走一步,也可以選擇先走兩步,則f(n) = f(n - 1) + f(n - 2)。有了這個公式,遞歸的解法很容易給出。
class Solution {
public:
int climbStairs(int n) {
if (n <= 2) return n;
return climbStairs(n - 1) + climbStairs(n - 2);
}
};
代碼很簡單,但是如果n比較大的時候,會出現上面提到的執行超時的問題,原因還是遞歸調用時重複執行問題。解決的辦法還是減少不必要的重複調用——利用緩存來解決問題。
class Solution {
public:
int climbStairs(int n) {
static vector<int> sCache = {1,2};
if (sCache.size() >= n) return sCache[n - 1];
sCache.push_back(climbStairs(n - 1) + climbStairs(n - 2));
return sCache[sCache.size() - 1];
}
};
再看一下上面推導出來的公式,實際上和斐波那契數列一樣。斐波那契除了用遞歸實現,更快速的實現是使用循環。
class Solution {
public int climbStairs(int n) {
if (n < 3) {
return n;
}
int a = 1, b= 2;
for(int i = 0; i < (n - 1) / 2; i ++) {
a = a + b;
b = b + a;
}
return n % 2 == 0 ? b : a;
}
}