动态规划&数塔取数&矩阵取数&背包问题&最大子段和&正整数分组
作者:互联网
动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。在面试笔试中动态规划也是经常作为考题出现,其中较为简单的DP题目我们应该有百分之百的把握顺利解决才可以。
一、动态规划定义
动态规划实际上是一类题目的总称,并不是指某个固定的算法。动态规划的意义就是通过采用递推(或者分而治之)的策略,通过解决大问题的子问题从而解决整体的做法。动态规划的核心思想是巧妙地将问题拆分成多个子问题,通过计算子问题而得到整体问题的解。而子问题又可以拆分成更多的子问题,从而用类似递推迭代的方法解决要求的问题。
二、动态规划的应用场景
有的问题过于抽象,或者过于啰嗦干扰我们解题的思路,我们要做的就是将题干中的问题进行转化(换一种说法,含义不变)。转化成一系列同类问题的某个解的情况,比如说:
题目:求一个数列中最大连续子序列的和。
我们要将这个原问题转化为:
状态定义:Fk是第k项前的最大序列和,求F1~Fn中最大值。
通过换一种表述方式,我们清晰地发现了解决问题的思路,如何求出F1~Fn中的最大值是解决原问题的关键部分。上述将原问题转化成另一种表述方式的过程叫做:状态的定义。这样的状态定义给出了一种类似通解的思路,把一个原来毫无头绪的问题转换成了可以求解的问题。
对于一个可拆分问题中存在可以由前若干项计算当前项的问题可以由动态规划来计算。
例题1:数塔取数问题
一个高度为N的由正整数组成的三角形,从上走到下,求经过的数字和的最大值。
每次只能走到下一层相邻的数上,例如从第3层的6向下走,只能走到第4层的2或9上。
该三角形第n层有n个数字,例如:
第一层有一个数字:5
第二层有两个数字:8 4
第三层有三个数字:3 6 9
第四层有四个数字:7 2 9 5
最优方案是:5 + 8 + 6 + 9 = 28
注意:上面应该是排列成一个三角形的样子不是竖向对应的,排版问题没有显示成三角形。
状态定义: Fi,j是第i行j列项最大取数和,求第n行Fn,m(0 < m < n)中最大值。
状态转移方程:Fi,j = max{Fi-1,j-1,Fi-1,j}+Ai,j。代码:
public class Test {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();// 元素数量
int max = 0;
int dp[][] = new int[n][n];
dp[0][0] = scan.nextInt();
for(int i=1; i<n; i++) {// 第一行只有一个元素,已经在dp[0][0]处赋值了
for(int j=0; j<=i; j++) {// j<=i
int num = scan.nextInt();
if(j == 0) {
dp[i][j] = dp[i-1][j] + num;
} else {
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-1]) + num;
}
max = Math.max(dp[i][j], max);
}
}
System.out.println(max);
}
}
例题2:矩阵取数问题
一个N*N矩阵中有不同的正整数,经过这个格子,就能获得相应价值的奖励,从左上走到右下,只能向下向右走,求能够获得的最大价值。例如:3 * 3的方格。
1 3 3
2 1 3
2 2 1
能够获得的最大价值为:11。
分析:假设我们定义f(int x,int y)表示从起点到第x行第y列的最优路径上的数之和,并假设这个矩阵是个二维数组A[][] (下标从1开始),我们考虑一下,我们如何才能到(x,y)?前一步要么到(x – 1, y), 要么到(x, y – 1),因为有且只有这两个位置能到(x,y),那么怎样才能得到f(x,y)?按我们前面说的,如果从起点达到(x,y)的最优路径要经过(x – 1,y)或者(x,y – 1)则,从起点到达(x – 1,y)或者(x,y – 1)的路径一定也必须是最优的。
那么按照我们对f的定义,我们有从起点达到x,y的最优路径有两种可能:
要么f(x – 1, y) + A[x][y],要么f(x, y – 1) + A[x][y]。
我们要取最优,那自然取较大的,因此有f(x, y) = max(f(x – 1, y) , f(x, y – 1) ) + A[x][y],这样原来要枚举指数条路径,现在对于每个位置只有两种情况啦。有了递推关系还不够,有初值才能求解。那我们看一下 f(1,1),显然这是在起点,没的选f(1,1) = A[1][1]。那么按照递推式 f(1,2) = max(f(0, y) , f(1,1)) + A[1][2], 但是我们对f(0, y)没有定义呀!考虑下实际意义,这表示要么我们从上面到达(1,2)要么从左面到达(1,2),可是上面没有位置过来啊,这种说明没的选。所以我们可以定义f(0, y) = -∞, 同理我们也可以定义f(x, 0) = -∞。代码:
public class Test {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int[][] dps = new int[n+1][n+1];
int[][] nums = new int[n+1][n+1];
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
nums[i][j] = scan.nextInt();
}
}
System.out.println("obtain Max Value");
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
dps[i][j] = Math.max(dps[i-1][j], dps[i][j-1]) + nums[i][j];
}
}
System.out.println(dps[n][n]);
}
}
例题3:背包问题
假设现有容量10kg的背包,另外有3个物品,分别为a1,a2,a3。物品a1重量为3kg,价值为4;物品a2重量为4kg,价值为5;物品a3重量为5kg,价值为6。将哪些物品放入背包可使得背包中的总价值最大?
思路:先将原始问题一般化,欲求背包能够获得的总价值,即欲求前i个物体放入容量为m(kg)背包的最大价值c[i][m]——使用一个数组来存储最大价值,当m取10,i取3时,即原始问题了。而前i个物体放入容量为m(kg)的背包,又可以转化成前(i-1)个物体放入背包的问题。下面使用数学表达式描述它们两者之间的具体关系。
表达式中各个符号的具体含义:
w[i] : 第i个物体的重量;
p[i] : 第i个物体的价值;
c[i][m] : 前i个物体放入容量为m的背包的最大价值;
c[i-1][m] : 前i-1个物体放入容量为m的背包的最大价值;
c[i-1][m-w[i]] : 前i-1个物体放入容量为m-w[i]的背包的最大价值;
由此可得:
c[i][m]=max{c[i-1][m-w[i]]+pi , c[i-1][m]},其中分为:
1.放入第i个物体:则c[i][m]=c[i-1][m-w[i]]+p[i],m-w[i]即背包放入第i个物体后的容量,p[i]即第i个物体的价值;
2.不放入第i个物体:则c[i][m]=c[i-1][m]。代码:
public class Test {
public static void main(String[] args) {
int m = 10;
int n = 3;
int w[] = {3, 4, 5};
int p[] = {4, 5, 6};
int c[][] = backPack(m, n, w, p);
for (int i = 1; i <=n; i++) {
for (int j = 1; j <=m; j++) {
System.out.print(c[i][j]+"\t");
if(j==m){
System.out.println();
}
}
}
}
/**
*
* @param m 表示背包的最大容量
* @param n 表示商品个数
* @param w 表示商品重量数组
* @param p 表示商品价值数组
* @return
*/
public static int[][] backPack(int m, int n, int[] w, int[] p) {
int[][] c = new int[n+1][m+1];// 表示前i件物品恰放入一个重量为m的背包可以获得的最大价值
for(int i=1; i<n+1; i++) {
for(int j=1; j<m+1; j++) {
// 当物品为i件重量为j时,如果第i件的重量(w[i-1])小于重量j时(即可以放入背包中),c[i][j]为下列两种情况之一:
// (1)物品i不放入背包中,所以c[i][j]为c[i-1][j]的值
// (2)物品i放入背包中,则背包剩余容量为j-w[i-1],所以c[i][j]为c[i-1][j-w[i-1]]的值加上当前物品i的价值
if(w[i-1]<=j) {
if (c[i-1][j] < (c[i-1][j-w[i-1]] + p[i-1]))
c[i][j] = c[i-1][j-w[i-1]] + p[i-1];
else
c[i][j] = c[i-1][j];
} else {
c[i][j] = c[i-1][j];
}
}
}
return c;
}
}
例题4:最大子段和
N个整数组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的连续子段和的最大值。
当所给的整数均为负数时和为0。
例如:-2,11,-4,13,-5,-2,和最大的子段为:11,-4,13。和为20。
由b[j]的定义可易知,当b[j-1]>0时b[j]=b[j-1]+a[j],否则b[j]=a[j]。故b[j]的动态规划递归式为:
b[j]=max(b[j-1]+a[j],a[j]),1<=j<=n。代码:
public class Test {
public static void main(String[] args) {
int[] nums = {-2, 11, -4, 13, -5, -2};
System.out.println(print(nums));
}
public static int print(int[] nums) {
int max = 0;
int[] b= new int[nums.length];
b[0] = nums[0];
for(int i=1; i<nums.length; i++) {
if(b[i-1] > 0) {
b[i] = b[i-1] + nums[i];
} else {
b[i] = nums[i];
}
max = Math.max(max, b[i]);
}
return max;
}
}
例题5:正整数分组
将一堆正整数分为2组,要求2组的和相差最小。
例如:1 2 3 4 5,将1 2 4分为1组,3 5分为1组,两组和相差1,是所有方案中相差最少的。
思路:典型的背包型DP问题,要使得差值最小,即尽可能接近总数和的一半,即拿物品去凑sum/2的背包体积。代码:
public class Test {
public static void main(String[] args) {
int nums[] = {1, 2, 3, 4, 5};
int c[][] = print(nums);
for (int i = 1; i <=8; i++) {
for (int j = 1; j <=8; j++) {
System.out.print(c[i][j]+"\t");
if(j==8){
System.out.println();
}
}
}
}
public static int[][] print(int[] nums) {
int n = nums.length;
int[][] c = new int[n+1][100];
int sum = 0;
for(int i=0; i<n; i++) {
sum+=nums[i];
}
for(int i=1; i<n; i++) {
for(int j=1; j<=sum/2; j++) {
if(j < nums[i]) {
c[i][j] = c[i-1][j];
} else {
c[i][j] = Math.max(c[i-1][j], c[i-1][j-nums[i]]+nums[i]);
}
}
}
return c;
}
}
标签:背包,数塔,nums,int,max,子段,System,取数,public 来源: https://www.cnblogs.com/yuanfei1110111/p/10354261.html