1、动态规划一般可分为线性动规,区域动规,树形动规,背包动规四类。
举例:
线性动规:拦截导弹,合唱队形,挖地雷,建学校,剑客决斗等;
区域动规:石子合并, 加分二叉树,统计单词个数,炮兵布阵等;
树形动规:贪吃的九头龙,二分查找树,聚会的欢乐,数字三角形等;
背包问题:01背包问题,完全背包问题,多重背包问题,分组背包问题,二维背包,装箱问题,挤牛奶(同济ACM第1132题)等;
(引用百科解释)
如果一类活动过程可以分为若干个互相联系的阶段,在每一个阶段都需作出决策(采取措施),一个阶段的决策确定以后,常常影响到下一个阶段的决策,从而就完全确定了一个过程的活动路线,则称它为多阶段决策问题。
各个阶段的决策构成一个决策序列,称为一个策略。每一个阶段都有若干个决策可供选择,因而就有许多策略供我们选取,对应于一个策略可以确定活动的效果,这个效果可以用数量来确定。策略不同,效果也不同,多阶段决策问题,就是要在可以选择的那些策略中间,选取一个最优策略,使在预定的标准下达到最好的效果。
多阶段决策问题中,各个阶段采取的决策,一般来说是与时间有关的,决策依赖于当前状态,又随即引起状态的转移,一个决策序列就是在变化的状态中产生出来的,故有"动态"的含义,称这种解决多阶段决策最优化问题的方法为动态规划方法。
动态规划方法是一个演变过程,最开始其实是由暴力递归开始,接着到记忆搜索,最后再到动态规划方法。实质上是一个空间换时间的变换过程。
2、下面第一个问题就是动态规划的一个基本问题,求解数组中多个数的值求和是否可以匹配给定的一个目标值,如果匹配只要返回True即可,也即只要找到一种匹配方案即可!
栗子如下:给定一个数组arr,和一个target,求解是否有满足的子集之和为target。
(此处用java,个人感觉不用numpy的python比较不方便。上代码 = = 我喜欢用代码看问题。。)
public class DP1 {
public static boolean dp_subset(int[] arr,int target){
if(arr==null){ //第一步先判断数组是否为空
return false;
}
int[][] dp = new int[arr.length][target+1];
/*
申请一个矩阵用来保存每次的结果
*/
for(int i=0;i<target+1;i++){
dp[0][i] = i == arr[0]?1:0;
//第一行表示的是数组第一个数是否在0-target之间,所在位置为1
//然后数组后一个的数可以说是依赖于数组前一个数也即前一行决定的
}
dp[0][0] = 1;
//第一列的位置表示目标值为0,为临界条件,所以该列的所有行的值都必须为1.
for(int j=1;j<arr.length;j++){
//注意从第二行开始,因为第一行已经初始化好了
for(int k=0;k<target+1;k++){ /
/从列标为0开始,一次遍历到最后一个位置
if(k<arr[j]){
//存在如果列表值(表示当前目标值) < 当前数组元素值时,直接搬上一行的结果来即可
dp[j][k] = dp[j-1][k];
}
else{
dp[j][k] = dp[j-1][k] | dp[j-1][k-arr[j]];
//每个值分为两种情况,选当前数组元素以及不选当前数组元素,
//只要有一个满足结果为目标值即可,所以用或,因为两个值要不为0要不为1,所以直接"|"
}
}
}
return dp[arr.length-1][target] == 1?true:false;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr1 = {99,199,1999,10000,39,1499};
int[] arr2 = {3,34,4,12,5,2};
System.out.println(dp_subset(arr1, 10238));
System.out.println(dp_subset(arr2, 13));
//仔细观察数组不难发现13没法拼凑出来
System.out.println(dp_subset(arr2, 15));
}
}
结果如下:(如果觉得例子过于简单,可以自己再构建)
true
false
true
3、第二个问题为找零钱问题。
有数组penny,penny中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim(小于等于1000)代表要找的钱数,求换钱有多少种方法。
给定数组penny及它的大小(小于等于50),同时给定一个整数aim,请返回有多少种方法可以凑成aim。
这里和上面的区别在于不仅要保存数组的该元素,还要保存数组元素的倍数,因为零钱可能由多张相同的构成,所以每次凡是碰到了数组元素的倍数都要进行判断。而每次保存的时候将方法数相加。注意每一次保存都要进行类似上面DP1一样的判断,即是否选择当前元素或元素的倍数,然后将选择与不选择的方法数相加即可。
(此处用java,个人感觉不用numpy的python比较不方便。上代码 = = 我喜欢用代码看问题。。)
public class DP2 {
public static int countWays(int[] penny, int n, int target) {
if(0 == n || null == penny)
return 0;
int[][] dp = new int[penny.length][target+1];
for(int i=0;i<target+1;i++){
dp[0][i] = i % penny[0] == 0?1:0;
}
for(int j=1;j<n;j++){
for(int k=0;k<target+1;k++){
if(k<penny[j]){
dp[j][k] = dp[j-1][k];
}else{
dp[j][k] = dp[j][k-penny[j]] + dp[j-1][k];
}
}
}
return dp[n-1][target];
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr = {1,2,4};
System.out.println(countWays(arr, 3, 3));
}
}
结果如下:(如果觉得例子过于简单,可以自己再构建)
2
4、题目如下:
HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)
求解该问题同样可以利用动态规划的思想去做,只不过每次的判断更加简单,做一次最大值的判断即可。
(此处用java,个人感觉不用numpy的python比较不方便。上代码 = = 我喜欢用代码看问题。。)
public class DP3 {
public static int FindGreatestSumOfSubArray(int[] arr){
if(arr.length==0){
return 0;
}
int max = arr[0];
int temp = arr[0];
for(int i=1;i<arr.length;i++){
temp = arr[i]>(arr[i]+temp)?arr[i]:(arr[i]+temp);
max = temp>max?temp:max;
}
return max;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr = {6,-3,-2,7,-15,1,2,2};
System.out.println(FindGreatestSumOfSubArray(arr));
}
}
结果如下:(如果觉得例子过于简单,可以自己再构建)
8