LeetCode動態規劃一:基本框架
阿新 • • 發佈:2020-11-01
動態規劃框架
動態規劃:Dynamic Programming
動態規劃問題的一般形式就是求最值 -> 窮舉
動態規劃的窮舉特點在於這類問題存在重疊子問題,如果暴力窮舉,效率極其低下。所以一般通過備忘錄或者DP table用空間來優化窮舉的過程,避免不必要的計算。
動態規劃三要素:重疊子問題、最優子結構、狀態轉移方程
重疊子問題:
最優子結構:
狀態轉移方程:窮舉的方式
思維框架:
明確base case -> 明確[狀態] -> 明確[選擇] -> 定義dp陣列、函式的含義
程式碼框架:
# 初始化 base case dp[0][0][...] = base # 進行狀態轉移 for 狀態1 in 狀態1的所有取值: for 狀態2 in 狀態2的所有取值: for ... dp[狀態1][狀態2][...] = 求最值(選擇1,選擇2...)
下面以例子方式詳解動態規劃的基本原理
斐波那契數列問題
509.斐波那契數
斐波那契數,通常用 F(n) 表示,形成的序列稱為斐波那契數列。該數列由 0 和 1 開始,後面的每一項數字都是前面兩項數字的和。也就是:
F(0) = 0, F(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
由題意可以推到出一下遞推公式:
方法一:暴力遞迴
//方法一:遞迴
int fib(int n)
{
// base case
if (n == 0 || n == 1)
{
return 1;
}
return fib(n - 1) + fib(n - 2);
}
方法二:遞迴 + 備忘錄(自頂向下)
//方法二:遞迴 + 備忘錄(自頂向下) int helper(int *rec_table, int n) { if (n == 0 || n == 1) { return n; } if (rec_table[n] != 0) { return rec_table[n]; } rec_table[n] = helper(rec_table, n - 1) + helper(rec_table, n - 2); return rec_table[n]; } int fib(int n) { if (n == 0 || n == 1) { return n; } int *rec_table = (int *)malloc((n + 1) * sizeof(int)); memset(rec_table, 0, (n + 1) * sizeof(int)); rec_table[0] = 0; rec_table[1] = 1; return helper(rec_table, n); }
方法三:動態規劃 + dp陣列
// 方法三:動態規劃,陣列/滾動變數
int fib(int n)
{
if (n == 0 || n == 1)
{
return n;
}
int *dp = (int *)malloc((n + 1) * sizeof(int));
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= n; i++)
{
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}