古典問題:3個月起每個月都生一對兔子,小兔子長到第三個月後每個月又生一對兔子,假如兔子都不死,問每個月的兔子總數為多少?
將兔子按年齡分為三組:
| 月份 | 小兔 | 中兔 | 大兔 | 總對數 | 狀態轉移 |
|---|---|---|---|---|---|
| 1 | 1 | 0 | 0 | 1 | 初始一對小兔 |
| 2 | 0 | 1 | 0 | 1 | 小→中 |
| 3 | 1 | 0 | 1 | 2 | 中→大,大生小 |
| 4 | 1 | 1 | 1 | 3 | 小→中,中→大,大生小 |
| 5 | 2 | 1 | 2 | 5 | ... |
| 6 | 3 | 2 | 3 | 8 | |
| 7 | 5 | 3 | 5 | 13 |
核心規律:第 n 月總數 = 第 n-1 月總數 + 第 n-2 月總數 (n ≥ 3)。這正是斐波那契數列:1, 1, 2, 3, 5, 8, 13, 21...
public class RabbitRecursive {
public static long fibonacci(int month) {
if (month <= 2) return 1;
return fibonacci(month - 1) + fibonacci(month - 2);
}
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
System.out.printf("第 %2d 月: %,d 對%n", i, fibonacci(i));
}
}
}
// 缺點:指數級時間複雜度 O(2^n),month=50 時需數小時
public class RabbitMemoization {
private static Map memo = new HashMap<>();
static { memo.put(1, 1L); memo.put(2, 1L); }
public static long fibonacci(int month) {
if (memo.containsKey(month)) return memo.get(month);
long result = fibonacci(month - 1) + fibonacci(month - 2);
memo.put(month, result);
return result;
}
// 時間 O(n),空間 O(n),解決重複計算
public class RabbitDP {
public static long fibonacci(int month) {
if (month <= 2) return 1;
long prev2 = 1, prev1 = 1, current = 0;
for (int i = 3; i <= month; i++) {
current = prev1 + prev2;
prev2 = prev1;
prev1 = current;
}
return current;
}
// 時間 O(n),空間 O(1),生產環境首選
public class RabbitMatrix {
// [[1,1],[1,0]]^n = [[F(n+1), F(n)], [F(n), F(n-1)]]
private static long[][] multiply(long[][] a, long[][] b) {
return new long[][]{
{a[0][0]*b[0][0] + a[0][1]*b[1][0], a[0][0]*b[0][1] + a[0][1]*b[1][1]},
{a[1][0]*b[0][0] + a[1][1]*b[1][0], a[1][0]*b[0][1] + a[1][1]*b[1][1]}
};
}
private static long[][] power(long[][] base, int n) {
long[][] result = {{1,0},{0,1}}; // 單位矩陣
while (n > 0) {
if ((n & 1) == 1) result = multiply(result, base);
base = multiply(base, base);
n >>= 1;
}
return result;
}
public static long fibonacci(int month) {
if (month <= 2) return 1;
long[][] base = {{1,1},{1,0}};
long[][] res = power(base, month - 2);
return res[0][0] + res[0][1]; // F(n) = F(n-1) + F(n-2)
}
// 時間 O(log n),適合 month > 10^6 等極大場景
斐波那契數列增長極快,long (第 92 個月溢出) 很快不夠用。使用 BigInteger:
public class RabbitBigInteger {
public static BigInteger fibonacci(int month) {
if (month <= 2) return BigInteger.ONE;
BigInteger prev2 = BigInteger.ONE, prev1 = BigInteger.ONE, current;
for (int i = 3; i <= month; i++) {
current = prev1.add(prev2);
prev2 = prev1;
prev1 = current;
}
return prev1;
}
public static void main(String[] args) {
System.out.println("第 100 月: " + fibonacci(100));
// 354224848179261915075
System.out.println("第 500 月位數: " + fibonacci(500).toString().length());
// 105 位
}
}
| 算法 | 時間複雜度 | 空間複雜度 | 適用場景 | 月份上限(目測) |
|---|---|---|---|---|
| 朴素遞歸 | O(2^n) | O(n) 棧 | 教學演示 | ~40 |
| 記憶化遞歸 | O(n) | O(n) | 理解 DP 思路 | ~10^5 (棧溢出風險) |
| 迭代 DP | O(n) | O(1) | 通用生產環境 | ~10^7 (時間受限) |
| 矩陣快速冪 | O(log n) | O(1) | 極大月份/競技編程 | ~10^18 (理論) |
F(n) = (φ^n - ψ^n)/√5,φ=(1+√5)/2。涉及浮點精度,大 n 不推薦直接用兔子問題是遞歸與動態規劃的絕佳入門教材。從指數級遞歸到常數空間迭代,再到對數級矩陣快速冪,每一步優化都對應著演算法設計的核心思想:消除重複子問題、空間換時間、數學結構加速。實際開發中,迭代 DP + BigInteger 已能覆蓋 99% 的業務場景,既簡潔又可靠。