DP定义:
动态规划是分治思想的延伸,通俗一点来说就是大事化小,小事化无的艺术。在将大问题化解为小问题的分治过程中,保存对这些小问题已经处理好的结果,并供后面处理更大规模的问题时直接使用这些结果。
动态规划具备了以下三个特点
把原来的问题分解成了几个相似的子问题。
所有的子问题都只需要解决一次。
储存子问题的解。
动态规划的本质:
是对问题状态的定义和状态转移方程的定义(状态以及状态之间的递推关系)
动态规划问题一般从以下四个角度考虑:
状态定义
状态间的转移方程定义
状态的初始化
返回结果
状态定义的要求: 定义的状态一定要形成递推关系。
一句话概括: 三特点四要素两本质
适用场景: 最大值/最小值, 可不可行, 是不是,方案个数
举例:
斐波那契数列定义:
斐波那契数列
F(n)=F(n-1)+F(n-2)(n>=2,n∈N*),其中F(1)=1,F(2)=1
输入描述:
一个正整数n
返回值描述:
输出一个正整数。
方法1:
递归实现
class Solution{
public:
int Fibonacci(int n){
// 初始值
if (n <= 0){
return 0;
}
if (n == 1 || n == 2) {
return 1;
}
// F(n)=F(n-1)+F(n-2)
return Fibonacci(n - 2) + Fibonacci(n - 1);
}
};
/*
递归的方法时间复杂度为O(2^n),随着n的增大呈现指数增长,效率低下
当输入比较大时,可能导致栈溢出
在递归过程中有大量的重复计算
*/
方法2:
动态规划
/*
方法二:动态规划
状态:F(n)
状态递推:F(n)=F(n-1)+F(n-2)
初始值:F(1)=F(2)=1
返回结果:F(N)
*/
class Solution2{
public:
int Fibonacci(int n){
// 初始值
if (n <= 0){
return 0;
}
if (n == 1 || n == 2) {
return 1;
}
// 申请一个数组,保存子问题的解,题目要求从第0项开始
int* record = new int[n + 1];
record[0] = 0;
record[1] = 1;
for (int i = 2; i <= n; i++){
// F(n)=F(n-1)+F(n-2)
record[i] = record[i - 1] + record[i - 2];
}
return record[n];
delete[] record;
}
};
class Solution {
public:
int Fibonacci(int n) {
/*
上述解法的空间复杂度为O(n)
其实F(n)只与它相邻的前两项有关,所以没有必要保存所有子问题的解
只需要保存两个子问题的解就可以
下面方法的空间复杂度将为O(1)
*/
//f(n-2)
int fn2=0;
//f(n-1)
int fn1=1;
int fn;
if(n<=1)
return n;
for(int i=2;i<=n;++i)
{
fn=fn1+fn2;
fn2=fn1;
fn1=fn;
}
return fn;
}
};