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