一般形式
求最值
核心问题
穷举
三要素
重叠子问题 →暴力穷举效率低下
最优子结构→通过子问题最值得到原问题最值
状态转移方程→正确的穷举
思考框架
明确【状态】 -> 定义dp数组/函数的意义 -> 明确【选择】-> 明确base case
实例
- fibnacci数列
// 递归
int fib(int N) {
if(N==1 || N==2) return 1;
return fib(N-1) + fib(N-2);
}
//备忘录解法, 自顶向下
int fib(int N) {
// 备忘录初始化
vector<int> memo(N+1, 0);
// 初始化最简情况
return helper(memo, N)
}
int helper(vector<int>& memo, int n) {
// base case
if(n==1 || n==2) return 1;
// 已经计算过
if(memo[n] != 0) return memo[n];
memo[n] = helper(memo, n-1) + helper(memo, n-2);
return memo[n];
}
// 动态规划, 自底向上
int fib(int N) {
vector<int> dp(N+1, 0);
dp[1] = dp[2] = 1;
for(int i = 3; i < N; i++) {
dp[i] = dp[i-1] + dp[i-2];
}
return dp[N];
}
// O(1)复杂度,只存储前两个状态
int fib(int N) {
// 边界
if(N==1 || N==2) return 1;
// 初始状态
int pre = 1, cur = 1;
for(int i = 3; i < N; i++) {
int sum = pre + cur;
pre = cur;
cur = sum;
}
return cur;
}
- 凑零钱问题
// 暴力递归(如何穷举)
int coinChange(vector<int>& coins, int N) {
if(N == 0) return 0;
if(N < 0) return -1;
int mini = N + 1;
for(int i = 0; i < coins.size(); i++) {
mini = min(mini, coinChange(coins, N - coins[i]));
}
return mini;
}
// 备忘录
int coinChange(vector<int>& coins, int N) {
if(N == 0) return 0;
if(N < 0) return -1;
vector<int> memo(N+1, N+1);
return helper(coins, N, memo);
}
int helper(vector<int>& coins, int n) {
if(n == 0) return 0;
if(n < 0) return -1;
if(memo[n] != N+1) return memo[n];
for(int i = 0; i < coins.size(); i++) {
memo[n] = min(memo[n], helper(coins, n-coins[i]));
}
return memo[n];
}
// 动态规划(如何聪明的穷举)
int coinChange(vector<int>& coins, int n) {
//
for(int i = 0; i < n; i++) {
for(int j = 0; j < coins.size(); j++) {
// 子问题无解
if(i - coins[j] < 0) continue;
dp[i] = min(dp[i], 1 + dp[i-coins[j]]);
}
}
return dp[n];
}