适用场景:
子问题与原问题性质相同
子问题相互关联(区别于分治算法)
求最优解,核心是穷举
动态规划特点:
- 重叠子问题
- 状态转移方程(最关键)
- 最优子结构
动态规划Dynamic program解题框架
- base_case
- 穷举状态
- 状态转移
经典案例一:斐波那契数列
f[1] = 1
f[2] = 2
f[n] = f[n-1]+f[n-2] // 状态转移方程
斐波那契额数列并不是一个正宗的动态规划问题,至少不是求最优解问题。
代码实现——递归法(暴力递归)
/*
斐波那契数列
暴力递归
*/
#include <stdio.h>
#include <stdlib.h>
int fib(int n) {
// base_case
if (n ==1 || n ==2) {
return n;
}
// 递归调用
return fib(n - 1) + fib(n - 2);
}
int main() {
int n = 200;
printf("fib(%d) = %d\n", n, fib(n));
return 0;
}
递归法效率太低。有大量重复性计算,时间复杂度O(2^n),耗时严重。
代码实现——递归法(自顶向下/带备忘录)
先自顶向下递归,再自底向上回溯。
/*
斐波那契数列
带备忘录的递归
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int helper(int *memo, int n) {
// base_case
if (n ==1 || n ==2) {
return n;
}
// 备忘录查询
if (memo[n] != 0) return memo[n];
memo[n] = helper(memo, n - 1) + helper(memo, n - 2);
return memo[n];
}
int fib(int n) {
// 备忘录初始化
int *memo = new int[n + 1];
memset(memo, 0, n+1);
// 进行带备忘录的递归
return helper(memo, n);
}
int main() {
int n = 50;
printf("fib(%d) = %d\n", n, fib(n));
return 0;
}
现在每隔节点就只被计算一次,那么时间复杂度为O(n)。
由于多了一个new内存分配。所以多了一个空间的开支,所以空间复杂度为O(n)。
带备忘录的递归解法是空间换时间
代码实现——迭代法(自底向上)
/*
斐波那契数列
带备忘录的递归
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int helper(int *memo, int n) {
// base_case
if (n ==1 || n ==2) {
return n;
}
// 备忘录查询
if (memo[n] != 0) return memo[n];
memo[n] = helper(memo, n - 1) + helper(memo, n - 2);
return memo[n];
}
int fib(int n) {
// 备忘录初始化
int *memo = new int[n + 1];
memset(memo, 0, n+1);
// 进行带备忘录的递归
return helper(memo, n);
}
int main() {
int n = 50;
printf("fib(%d) = %d\n", n, fib(n));
return 0;
}