目录:
(1)用动态规划算法解决背包问题
有一个背包,容量为4,要求达到的目标为装入的背包的总价值最大,并且重量不超出,同时装入的物品不能重复。
图一
图二
核心思想
将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法。且经分解得到的子问题往往不是互相独立的。
假设
i:商品。
j:重量。
v[i]:第i个商品的价值(价格G)。
w[i]:第i个商品的重量。
v[i][j]:在前i个商品中,能够装入容量为j的背包中的最大价值。
公式
(1)v[i][0]=v[0][j]=0;
表示填入表的第一行和第一列是0,如图二。
(2)当w[i]>j时:v[i][j]=v[i-1][j];
当准备加入新增的商品的容量大于当前背包的容量时,就直接使用上一个单元格的装入策略。
(3)当j>=w[i]时:v[i][j]=max{v[i-1][j],v[i]+v[i-1][j-w[i]];
当准备加入新增的商品的容量小于当前背包的容量时,采用以上公式计算。
——v[i-1][j]:就是上一个单元格的装入的最大值;
——v[i]:表示当前商品的价值;
——v[i-1][j-w[i]]:装入v[i]后,背包中余剩的容量为[j-w[i]],此时在v[i-1],即上一个单元格中寻找重量为[j-w[i]]的格子;
举例
求v[3][4]:即图二右下角最后一格。
此时i=3.j=4,含义为:在前3个商品中,能够装入容量为4的背包中的最大价值。
w[i]=w[3]=3,则与当前的背包容量进行比较:w[i]<j,符合公式(3)。
因此v[i][j]=v[3][4]=max{v[2][4],v[3]+v[2][4-3]}=max{3000,2000+1500}=2000+1500。
所以v[3][4]=3500。
代码演示
public class KnapsackProblem {
public static void main(String[] args) {
//物品的重量
int[] w = {1, 4, 3};
//物品的价值
int[] val = {1500, 3000, 2000};
//背包的容量
int m = 4;
//物品的个数
int n = val.length;
//表示在前i个物品中能够装入容量为j的背包中的最大价值,其中有一行和一列为0,因此要加1
int[][] v = new int[n+1][m+1];
//为了记录放入商品的情况,我定义一个二维数组
int[][] path = new int[n+1][m+1];
/**
* 公式1
* 初始化第一行和第一列,可不去处理,因为默认为0。
*/
for(int i = 0; i < v.length; i++){
//将第一列设置为0
v[i][0] = 0;
}
for(int i = 0; i < v[0].length; i++){
//将第一行设置为0
v[0][i] = 0;
}
//根据上面的三个公式来进行动态规划处理,从1开始,因为第一行和第一列都为0
for(int i = 1; i < v.length; i++){
for (int j = 1; j < v[0].length; j++){
/**
* 公式2
* 因为我们的程序是从1开始的,因此原来公式中的w[i]要修改澄w[i-1]
*/
if(w[i-1] > j){
v[i][j] = v[i-1][j];
} else {
/**
* 公式3
* 同上,val[i]要修改成val[i-1]
* 但是,用max函数的话,我们没法判断得到的结果是左边的数还是右边的数,进而没法记录物品放到背包的情况
* 因此将其修改为简单的if-else语句
*/
// v[i][j] = Math.max(v[i-1][j],val[i-1] + v[i-1][j-w[i-1]]);
if(v[i-1][j] < val[i-1] + v[i-1][j-w[i-1]]){
v[i][j] = val[i-1] + v[i-1][j-w[i-1]];
//把该结果记录到path中
path[i][j] = 1;
} else {
//仅需记录上面最优的情况
v[i][j] = v[i-1][j];
}
}
}
}
//输出表
for(int i = 0; i < v.length; i++){
for(int j = 0; j < v[i].length; j++){
System.out.print(v[i][j] + " ");
}
System.out.println();
}
//输出最后我们是放入的哪些商品,但这样会把所有符合公式3的放入情况全部输出,而我们仅需要最后一个(最优解)
// for(int i = 0; i < path.length; i++) {
// for (int j = 0; j < path[i].length; j++) {
// if(path[i][j] == 1){
// System.out.printf("第%d个商品放入到背包\n", i);
// }
// }
// }
//因此我们获取行和列的最大下标,从后往前遍历
int i = path.length - 1;
int j = path[0].length - 1;
while (i > 0 && j > 0 ){
if(path[i][j] == 1){
System.out.printf("第%d个商品放入到背包\n", i);
//放完后背包容量应减少
j -= w[i-1];
}
i--;
}
}
}
输出结果
(2)用动态规划算法解决最大子序和问题
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组 是数组中的一个连续部分。
子数组 是数组中的一个连续部分。
示例
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大为 6 。
原理
(1)若之前的求和数小于零,那么加上一个负数,会比原来更小,因此将求和数等于当前的数;而加上一个正数,求和后的结果会比当前的正数小,因此将求和数等于当前的数。
(2)若之前的求和数大于零,那么就将求和数和下一个数相加,得到新的求和数放入数组中。
(3)遍历求和数数组,得到最大求和数。
总结:若前一个元素大于零,则将其加到当前元素上。
代码演示
通过新建的数组dp来表示以 nums[i] 结尾的连续子数组的最大和。
若dp[i-1],即前一个求和数大于零,那么把下一个数num[i]加上;
若dp[i-1],即前一个求和数小于零,则抛弃之前的求和数,从num[i]开始算起,即dp[i]=nums[i]。
public class Solution2 {
public static int maxSubArray(int[] nums) {
int length = nums.length;
//表示以 nums[i] 结尾的连续子数组的最大和。
int[] dp = new int[length];
dp[0] = nums[0];
//若前一个元素大于零,则将其加到当前元素上
for(int i = 1; i < length; i++){
if(dp[i-1] > 0){
dp[i] = dp[i-1] + nums[i];
}else {
dp[i] = nums[i];
}
}
Arrays.sort(dp);
return dp[length-1];
}
public static void main(String[] args) {
int[] a = {-2,1,-3,4,-1,2,1,-5,4};
System.out.println(maxSubArray(a));
}
}