什么是动态规划
-
什么是动态规划?
动态规划 dynamic programming 简称DP。其基本思想是:将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子问题的解得到原问题的解;对于重复出现的子问题,只在第一次遇到的时候对它进行求解,并把答案保存起来,让以后再次遇到时直接引用答案,不必重新求解。动态规划算法将问题的解决方案视为一系列决策的结果。 -
动态规划的步骤
1.确定状态
2.定义状态转移方程
3.求得最优解
为什么要用动态规划
提到动态规划,我们就不得不和优化相关联起来,动态规划本质也是利用空间来换时间的思想,先将子问题存储起来,后面求解较大规模的问题的时候可以直接利用子规模问题的解。这样就节省了重复计算的时间,同时也可将复杂的问题转化为一个个简单的小问题。
如何利用动态规划解决问题
下面给出几个案例来分析如何使用动态规划
1.斐波那契数列
这题的解决方案有很多,有递归,迭代相加,动态规划,这里主要分析动态规划如何解决此问题。
1.确定DP状态
根据斐波那契函数,我们可以看出第一项和第二项的值恒为1,后面每一项都是前两项的和。因此我们定义一个dp数组来存储每一次前两项运算所得的值,也就是每个子状态的值。可以这么定义:int[] dp。默认前两项目是固定的值,所以dp[1]=1,dp[2]=1;
2.定义状态转移方程
根据函数方程我们很明显得能确定状态转移方程是 下一次的值等于前两次值之和,dp[i]=d[i-1]+dp[i-2]
3.求得最优解
根据状态转移方程,我们即可写出算法的全过程,并求得dp[n]的值,下面给出动态规划算法的java代码:
public class Solution {
public int Fibonacci(int n) {
//1和2是常数函数,直接返回
if(n<3) return 1;
int[] dp = new int[n+1];
//先规定好前两项
dp[1]=dp[2]=1;
//利用动态规划,逐个求出后面的值
for(int i=3;i<=n;i++){
dp[i] = dp[i-1]+dp[i-2];
}
return dp[n];
}
}
2.连续子数组的最大和
1.确定DP状态
此题没有给出函数方程,但是从题干中,我们不难可以推断出,用一个dp数组来存放第 i 个连续子数组的最大值,因为我们可以将i从0到n逐个遍历,利用状态转移方程,我们即可比较出在 i 位置上的最大值,并存放到dp[i]的位置。
2.定义状态转移方程
我们可以将dp[i]存储为以arr[i]为结尾的连续子数组的最大值,注意,dp[i]存储的必须是以arr[i]为结尾的连续子数组的最大和,而不是由前i个数字组成的数组的最大和,这里不能没有arr[i]参与,为什么需要这么设计呢?因为这里主要是为了判断连续子数组的起点到底需要从哪里开始.那,为什么又需要这么判断呢?因为是连续的子数组啊,由arr[i+1]想要组成连续数组必须要得看以arr[i]组成的连续子数组啊,不然就不连续了咯.如果arr[i]组成的连续子数组最终和是负的,这反而会让arr[i+1]求和后变小了,那干脆不如下标直接从arr[i+1]开始算了.这下应该理解dp[i]为什么需要是以arr[i]为结尾的连续子数组的最大值了吧.
我们举个例子,比如[1,-2,3].我们一眼就能看到最大的连续子数组其实就是[3].那么它的起点就从3开始了,为什么能确定是从3开始的呢?因为我们发现,以-2为结尾的连续子数组的最大值是-1,它根3求和会让3变小,所以起点就需要从3开始了.
那么如何确定这个最大值呢?这时候我们就需要状态转移方程,同样我们需要利用上一个子问题的解,来求得下一个状态的解,假设输入的数组定义为int[] arr那么我们可以定义状态转移方程为dp[i] = max(dp[i],dp[i-1]+arr[i]),这样我们就可以求得dp[i]位置的最大值到底是 (dp[i-1]的最大值+arr[i]) or dp[i] 的值本身就是最大,因为前面可能有负数,所以不一定前面的值累加就一定是最大。
此外,很多人可能会有疑问,为什么组成子数组要从第0个开始组织,从后面的某一位置开始组成子数组不是会更大吗?这里其实是大家对概念进行了混淆,要知道我们进行动态规划的目标是为了将问题给缩小,缩小范围后的数组其实也是为了求解连续子数组的最大和,当我们将范围缩小到只有一个数字的时候,那么最大和肯定是它这个数字本身.然后我们再将范围进行扩大,追个进行求解.我们从第0号位置开始组成连续子数组完全是习惯问题,仅仅是为了方便,若我们乐意,我们也完全可以从数组的最后一个数字开始,从一个数字开始逐步增加子数组的大小.
我们前面所提到从后面某一位置开始组成子数组更大,可能只是从第一个数字开始向后组成的某一个子动态规划的结果.比如 -1,1,2,我们会认为,1,2组成的子数组明显不是最大嘛,是的,它其实就是子动态规划-1,1,2求连续子数组的最大和的解.这里我们可以细心体会.
3.求得最优解
值得注意的,动态转移方程只能获取当前到第i个子数组的最大值,如果要求得整个数组的最大值,我们还需要维护一个最大值maxval,来记录所有规划过程中和最大的连续子数组的值。
下面给出动态规划算法的java代码:
import java.util.*;
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
//记录到下标i为止的最大连续子数组和
int[] dp = new int[array.length];
dp[0] = array[0];
int maxVal = array[0];
for(int i=1;i<array.length;i++){
//状态转移:连续子数组和最大值
dp[i] = Math.max(dp[i-1]+array[i],array[i]);
//维护最大值
maxVal = Math.max(dp[i],maxVal);
}
return maxVal;
根据两组案例,相信我们对动态规划一定有了深刻的了解了。