Dynamic Programming
什么是动态规划?动态规划就是将一个大问题不断向下拆分成小问题,直到拆分出的小问题可以求出其解,然后将小问题的解不断的向上合并,最终得到大问题的解决方案。
动态规划三要素:
1.问题的阶段
2. 每个阶段的状态
3. 从前一个阶段转化到后一个阶段之间的递推关系
----递推关系必须是从次小的问题开始到较大的问题之间的转化
f(n,m)=max{f(n-1,m),f(n-1,m-w[n])+p(n,m)}
DP特性
• 重复子问题
• 无后效性(后面的依赖前面,但前面的不可依赖后面)
• 最优子结构
斐波那契数列
1 1 2 3 5 8 13 。。。
F(N)=F(N-1)+F(N-2)
F(1)=1 F(2)=1
递归写法
static int Fib(int n) {
if(n==1 || n==2)
return 1;
else return Fib(n-1)+Fib(n-2);
}
递归写法存在很多重复计算,可以考虑用一个数组保存之前的计算结果--》
递归DP: (从后往前计算)
int[] arr=new int[N+1];
static int Fib(int n) {
if(arr[n]>0)
return arr[n];//大于0表示这个值已经计算过,直接返回
if(n==1 || n==2)
return arr[n]=1;
else return arr[n]=Fib(n-1)+Fib(n-2);
}
考虑两种情况:
1)N值特别大时,计算出的值int型数组可能存不下,int需要改为long
2)N值特别大时,比如1亿(需占用380M内存),存不下时,需要考虑节省空间。
迭代DP:(从前往后算)
int[] arr=new int[N+1];
static int Fib() {
for(int i=1;i<=N;i++) {
if(i==1 || i==2)
arr[i]=1;//初始条件,为常数,一般写在for循环外面
else
arr[i]=arr[i-1]+arr[i-2];
}
return arr[N];
}
迭代DP,减少存储空间,只需要三个数组大小就可以存储(滑动窗口)
滑动窗口
static int[] arr=new int[3];
static int Fib(int n) {
for(int i=0;i<n;i++) {
if(i==0 || i==1)
arr[i]=1;//一般写在for循环外面
else
arr[i%3]=arr[(i-1)%3]+arr[(i-2)%3];
}
return arr[(n-1)%3];
}
两种常见DP
最长递增子序列LIS
len[i]=max(len[j])+1 a[i]>a[j] 0
最大连续子序和
dp[i]=Math.max(dp[i-1]+arr[i],arr[i])
max=Math.max(dp[i],max)
DP解题步骤
一般解题步骤:
1.利用穷举搜索法解决给定问题
2.使用制表法只计算1次重复子问题
时间复杂度:
子问题的个数*单个子问题的时间复杂度
空间复杂度:
制表的大小
| 递归动态规划 | 迭代动态规划 | |
| 优点 | 代码更直观 |
代码长度更短 |
| 不考虑子问题的依赖关系和计算顺序 | 没有递归调用负载,执行更快 | |
| 有时候只需求解一部分值得解就可以得出答案 | 可使用滑动窗口. | |
| 缺点 | 需留意方法栈溢出 |
代码不易理解 状态方程不易发现 |
动态规划解题步骤:
1.暴力搜索
分步骤:
1)初始条件:前几步
2)递推条件:重复步骤
----》根据上面这两步可以做出递归,然后考虑是否有重复计算,进行优化
2.重复子问题-》DP
3.写DP
a.递归DP
1)定义表(数组)
2)递归
2.1 先判断是否计算过,return
2.2 初始条件,进行赋值,return
2.3 重复递归调用,return
b.迭代DP
1)定义表(数组)
2)初始条件赋值
3)动态算新值(for循环,从前往后)
4)return最后一个值
本文介绍了动态规划的概念,强调了其重复子问题、无后效性和最优子结构的特性,并通过斐波那契数列的递归与迭代DP解法进行了详细阐述。还探讨了递归DP和迭代DP的空间优化策略,如滑动窗口,并总结了动态规划的解题步骤。

被折叠的 条评论
为什么被折叠?



