LeetCode动态规划一:基本框架
动态规划框架
动态规划: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];
}