动态规划是什么
动态规划(Dynamic programming,简称DP)是通过把原问题分解为相对的简单的子问题来求解复杂问题的一种方法。(来自维基百科)
动态规划常常适用于有重叠子问题或最优子问题的问题,时间复杂度往往比暴力法小很多。
下面通过几个具体问题来介绍。
问题1:斐波那契数列
斐波那契数列又称兔子?数列,是指这样一个数列:1,1,2,3,5,8,13....
除第1,2两项外,其他所有项都是前两项之和,现在求第n项是多少。
通过四个步骤来分析问题
1.寻找状态
寻找状态就是指将原问题分解为简单的子问题,子问题之间存在着这样或那样的联系。对于本问题来说,状态就是指第n项依赖于n-1和n-2项。
2.状态转移方程(重要!!)
在此数列中,第n项的状态依赖于第n-1和第n-2项的状态,因此状态转移方程为f(n) = f(n-1)+f(n-2)
。
3.初始条件和边界
我们需要找到状态转移开始或停止的地方,对于本题而言,初始条件就是n=1和n=2
时f(n)=1
。
4.计算顺序
如果我们想要得到第n项,就需要知道n-1和n-2项。以此类推,直到n=1和n=2。因为后面的值依赖于前面,因此我们需要从前往后计算。
递归解法
根据上面的分析可以写出递归解法。
#include <iostream>
using namespace std;
class Soultion {
public:
int f(int n) {
if (n == 1)
return 1;
if (n == 2)
return 1;
return f(n - 1) + f(n - 2);
}
};
递归解法存在的问题
假设n=10,那么求f(10)就要知道f(9)和f(8)…依次类推,如下图所示。
可以看到在递归过程中存在很多重复计算,时间复杂度为o(2n),而动态规划要做的,就是去除这些重复计算,每个结果只计算1次并保存下来,重复利用。
动态规划解法
根据上面四个步骤的分析,可以这样写动态规划的代码。可以看到,每个f(n)都只计算了一次,时间复杂度为o(n)。
#include