动态规划
分治:将问题划分成互不相交的子问题,然后递归地求解子问题,最后将子问题组合起来得到原问题解。(eg.归并排序)
动态规划:和分治类似,但应用于子问题重叠的情况,即不同的子问题具有公共的子问题(在这种情况下使用分治会重复执行很多不必要的工作)。所以动态规划只对每个子问题求一次解,将结果存在一个表格中,需要使用时直接查表即可。(DP常常使用一维或二维表格存储中间结果值)
- 以时间换取空间效率
- 常用于求解最优化问题
DP经典问题
斐波那契数列
又称黄金分割数列,以兔子繁殖为例引入,又称“兔子数列”。指的是这样一组数字:1,1,2,3,5,8,13,……
F
(
n
)
=
{
1
,
n
=
1
,
2
F
(
n
−
1
)
+
F
(
n
−
2
)
,
n
≥
3
F(n)=\begin{cases} 1,n=1,2\\ F(n-1)+F(n-2), n\geq3\end{cases}
F(n)={1,n=1,2F(n−1)+F(n−2),n≥3
递归写法
递归的实现方式,会执行大量重复的函数调用,时间效率很低,运行时间为指数时间。
// 给定n,求F(n)
int DP_Solution::Fibonacci(int n)
{
if(n <= 1) return n;
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
迭代写法
对于求第n项值,需要保存第n-1和第n-2项的值,牺牲一点内存,换取多项式的运行时间。
int DP_Solution::Fibonacci2(int n)
{
if(n <= 1) return n;
int f1 = 0;
int f2 = 1;
int fn = 0;
for(int i = 2; i <= n; i++){
fn = f2 + f1;
f1 = f2;
f2 = fn;
}
return fn;
}
测试用例
int main()
{
DP_Solution dp_s;
time_t start, end;
start = time(NULL);
int res = dp_s.Fibonacci(40);
end = time(NULL);
cout << res << endl; // 102334155
cout << "time: " << (end - start) << "s" << endl; // 1 s
time_t start1, end1;
start1 = time(NULL);
int res2 = dp_s.Fibonacci2(40);
end1 = time(NULL);
cout << res2 << endl; // 102334155
cout << "time: " << (end1 - start1) << "s" << endl; // 0 s
}
跳台阶
问题:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级台阶总共有多少种跳法?
思路:把n级台阶的跳法种数看成n的函数F(n),n=1的时候,F(n) = 1;n=2时,F(n)=2;n>2时,第一次跳有两种不同的选择,第一种是跳一级,那么F(n)就等于跳剩下的n-1级台阶种数,第二种是跳2级,那么F(n)就等于跳剩下的n-2级台阶种数,所以F(n) = F(n-1) + F(n-2)。
F
(
n
)
=
{
1
,
n
=
1
2
,
n
=
2
F
(
n
−
1
)
+
F
(
n
−
2
)
,
n
>
2
F(n)=\begin{cases} 1,n=1\\ 2,n=2\\ F(n-1)+F(n-2), n>2\end{cases}
F(n)=⎩⎪⎨⎪⎧1,n=12,n=2F(n−1)+F(n−2),n>2
int DP_Solution::JumpSteps(int n)
{
if(n <= 2) return n;
int f1 = 1;
int f2 = 2;
int fn = 0;
for(int i = 3; i <= n; i++){
fn = f1 + f2;
f1 = f2;
f2 = fn;
}
return fn;
}