什么是动态规划
动态规划的主要思想是把问题划分为一个个子状态,一个状态的最优解往往是基于其前一个状态的最优解。两个状态之间的关系,我们就称之为状态转移方程。这里引出了状态和状态转移方程的概念:状态是一个当前的值,这个值是通过前一个值以及状态转移方程推得的。在解决动态规划问题的时候,我们往往会把问题建模为一个一维数组或是二维数组,处理完边界值之后,就可以通过前一个状态和后一个状态的递推关系循环解出轴上的一个个状态值。如果说贪心算法是为了达到目的追求局部最优,简单粗暴,那么动态规划就是一种相对严密,统筹全局的算法。
动态规划的特点
1、空间换时间:动态规划把最终问题的求解分解为一个个子结构,我们可以称之为最优子结构,在求解出最终结果前,我们把之前的每一个状态的最优解存储在了数组中,后面的状态的求解基于之前的结果,这样可以节省时间,但需要一些存储空间来放这些值,这就是空间换时间。
2、题目提供的往往是一些具有一定属性的对象,然后用他们去达成某种总体最优的目标。
3、动态规划问题的求解数组可以是一维的也可以是二维的。
4、重叠子结构:在求解一个大问题的过程中需要多次求解某个小问题,而小问题的解我们已经得到,直接取出来使用即可。
5、无后效性:某个状态的求解只与它之前的状态有关,而与它后面的状态没有关系。
动态规划解题步骤
1、确定状态:首先找到题目中变化的量,和求解目标对应的变量,一般我们可以基于这些量构建一个一维或者二维数组。
2、确定递推方向:分析题目要求,确定求解数组状态的循环初始位置和递推方向。
3、处理数组边缘值:定义数组后,往往我们会把边缘值置0。若目标值为0,对应的状态的解自然也是0,边界的0的部分需要我们主动加上去,大家也要格外注意一下dp下标和数组下标因为引入0导致的偏移。
4、在循环中求解数组:即通过循环,去递推一个个子状态的值。这个时候需要注意状态转移方程,状态之间的关系变化。
5、选出需要的最优解。
动态规划题型1:钱币选择问题
问题描述:给你若干面值为1,3,5,数量不限的钱币,给一个目标值,需要用最少的钱币数量凑齐目标值。
解题思路:使用动态规划,先确定凑0元需要0个钱币,凑1元至少需要一个1元钱币;凑2元至少两个1元钱币;凑3元时选择一个3元硬币比选择3个1元硬币更优,故选择一个3元硬币;凑四元的时候我们选择用到前面的结果,只要再加上一元硬币即可,当然在选择这个方案前我们比对过(4-1)元、(4-3)元、(4-5)元的几种情况。由于之前的计算结果都已经最优,所以我们只要考虑最后一张钱币拿的是一元、三元、还是五元即可。这样一来,我们可以递推得到任意状态的解。
算法实现:
/**
* 动态规划1:钱币选择问题
*/
public void dp1()
{
int[] money={
1,3,5,7};
int sum=24;
int result = DP1(money,sum);
System.out.println(result);
}
/**
* 动态规划1:钱币选择问题
* @return
*/
private int DP1(int[] money,int sum)
{
int[] number = new int[sum+1];
for(int m =0;m<number.length;m++)//初始化一个较大的值
{
number[m]=m;
}
for(int i=1;i<=sum;i++) //循环获取不同面值对应的最小数量
{
for(int j=0;j<money.length;j++) //循环拿可能情况进行比对
{
if(i>=money[j]&&number[i-money[j]]+1<number[i]) //满足更小条件则赋值
number[i]=number[i-money[j]]+1; //如果发现有更小的情况就更新数值
}
}
return number[sum];
}
动态规划题型2:求三角形路径数字最大和(自顶至底)
题目描述:给你一个类似二叉树的结构,每个节点都有相应的值,现求自顶至底的路径的最大数字和。
解题思路:其实这题自顶至底或者自低至顶都是可以的,我们这里选择前者来求解。我们只要把数据存入一个二维数组,然后从顶端往下依次推得到达每个节点的最大数字和。
算法实现:
/**
* 动态规划2:求三角形路径数字最大和(自顶至底)
*/
public void dp2()
{
int[][] a = new int[5][5];
Scanner sc =