一.经典背包问题(零一背包)
给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。问题的名称来源于如何选择最合适的物品放置于给定背包中。(每个物品只能选择一次)
解题思路:
1.建立二维dp数组,如dp[i][j](含义为:在遍历遇到物品i(物品的下标)时,背包的重量为j时,此时背包的总价值为dp[i][j];(如dp[1][1]的含义为在遇到物品1,背包重量为j时,总价值为dp[i][j])
2.确定递推公式
dp[i][j] = Math.max(dp[i - 1][j] , dp[i - 1][ j - weight[i]] + value[i]);
//其中dp[i - 1][j - weight[i]] + value[i] 可以这样理解:
//此时背包最大容量为j ,若要将物品j放入背包则需要为其腾出相应的空间weight[i];
3.根据实际情况初始化数组;
for(int i = 0 ; i <= MAX_WEIGHT; i++){ //MAX_WEIGHT为背包所能装的最大的容量
if(weight[0] <= i) dp[0][i] = weight[i]; //当背包容量大于的第一个物品的重量时
else dp[0][i] = 0; //可以将物品放入背包
}
for(int i = 0; i < weight.length; i++){ //weigth.length为物品的数量
dp[i][0] = 0; //当背包容量为0时,背包中价值一直为0
}
4.为了便于理解,外层先遍历物品,里层遍历背包的重量
for(int i = 1 ; i < weight.length ; i++){ //物品
for(int j = 0 ; j < MAX_WEIGHT ; j++){ //背包
if(j >= weight[i])
dp[i][j] = Math.max(dp[i - 1][j] , dp[i - 1][ j - weight[i]] + value[i]);
else dp[i][j] = dp[i- 1][j];
}
}
//其中dp[i - 1][j - weight[i]] + value[i] 可以这样理解:
//此时背包最大容量为j ,若要将物品j放入背包则需要为其腾出相应的空间weight[i];
5.举例例子,打印dp数组确定对错性。
(ps:在遍历顺序中内外层的遍历可互换,不影响结果)
额外补充:
可以背包问题中的dp二维数组压缩成一维的dp数组(滚动数组),则变化成dp[j] ,其含义为背包最大重量为j时,所背最大的价值为dp[j]
则初始化为 dp[0] = 0;
遍历顺序为
//此处内外层不可互换,且必须为倒序遍历,否则变为完全背包为问题
for(int i = 1 ; i < weight.length ; i++){ //物品
for(int j = MAX_WEIGHT ; j > weigth[i] ; j--){ //背包
dp[j] = Math.max(dp[j] ,dp[j - weight[i]] + value[i]);
}
}
(详细可看:代码随想录:带你学透01背包问题(滚动数组篇) | 从此对背包问题不再迷茫!_哔哩哔哩_bilibili)
二,力扣中零一背包的变式及其解题思路
1.分割等和子式
主要解题思路:
将nums数组抽象为物品集,其中同一物品的重量和价格两者相等,算出nums数组数组总和得到sum,其中target = sum / 2 ,将问题转化成单纯的经典的零一背包问题:有一个物品集为nums ,求在最大容量target的情况下,背包中的物品的价值是否能达到target。
具体代码
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
int lengths = nums.length;
for(int i = 0; i < lengths ;i++){
sum += nums[i];
}
if(sum % 2 == 1) return false;
int target = sum / 2;
int [] dp = new int [target + 1];
for(int i = 1 ; i < lengths; i++){
for(int j = target; j >= nums[i] ; j--){
dp[j] = Math.max(dp[j],dp[j - nums[i]] + nums[i]);
}
}
if(dp[target] == target) return true;
return false;
}
}
2.最后一块石头的重量II
主要解题思路:
与上一题相似,stone[i] 抽象成重量价值相统一的物品集,难点在于找出target值,根据题意,所要求的是碰撞后剩下的石头重量,可以将石头堆尽可能分为重量相等的两堆,即target = sum / 2;
将问题转化成单纯的经典的零一背包问题:有一个物品集为stone ,求在最大容量target的情况下,求背包中的物品最大价值。(最后返回:sum - 2 * dp[target])。
具体实现代码:
class Solution {
public int lastStoneWeightII(int[] stones) {
int sum = 0 ;
int lengths = stones.length;
for(int i = 0; i < lengths ;i++){
sum += stones[i];
}
int target = sum / 2;
int [] dp = new int [target + 1];
dp[0] = 0;
for(int i = 0; i < lengths;i++){
for(int j = target ; j >= stones[i]; j--){
dp[j] = Math.max(dp[j] , dp[j - stones[i]] + stones[i]);
}
}
return sum - 2 * dp[target];
}
}
3.目标和
解题思路:
与前面的题目一样,将nums转化为物品集 ,痛点仍然是找出背包最大值target ,这里的思路与石头那道有点相似,可以将nums分为两堆进行相减,得到目标和,将正数堆设为left ,负数堆设为right ,则 left - right == 目标和 ,left + right == sum , 两条式子可以解出两个未知量 ,则
left = (sum + 目标和) / 2 ,得出背包最大值target(target = left);
但是不同于石头的是这里求的是达成目标和的方式的种数,现在dp数组的含义发生变化:
dp[j] :装满背包容量为j的背包,一共有dp[ j ]方法
则将递推公式修改为
dp[j] += dp[j - nums[i]];
初始化为
dp[0] = 1;
(为啥子dp[ 0 ] == 1 ? 可以想象为当物品数量为1,物品的重量刚好为0 ,则dp[0] = 1 ,实在想不出更好的原因和例子(苦笑))
则现在将其转化成有一个物品集为nums ,装满最大容量为j的背包 ,最多有多少种方法。
实现代码如下:
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int lengths = nums.length;
int sum = 0;
for(int i = 0 ; i < lengths;i++){
sum += nums[i];
}
int left = (sum + target) / 2;
if((sum + target) % 2 == 1 || left < 0) return 0;
int [] dp = new int [left + 1];
dp[0] = 1;
for(int i = 0; i < lengths;i++){
for(int j = left ; j >= nums[i];j--){
dp[j] += dp[j - nums[i]];
}
}
return dp[left];
}
}
4.一和零(零一背包的变式:多重背包)
解题思路:
本题仍是考察我们的抽象思维,如果能将其抽象为背包问题就已经成功了一半;
将strs数组中的字符串中的0抽象成重量weight,1抽象为value,因为要同时考虑两个维度则将
dp数组变成二维dp[ i ][ j ] ,含义:
背包最大重量为i ,背包最大价值为 j 时 ,背包最多能装 dp[ i ][ j ]个物品;
递推公式为 :(这里dp[ j ][ k ] 即为 dp[ i ][ j ])
dp[j][k] = Math.max(dp[j - weight[i]][k - value[i]] + 1,dp[j][k]);
代码实现:
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
int lengths = strs.length;
int [] weight = new int [lengths];
int [] value = new int [lengths];
//算出每个物品(每个字符串)的 0 和 1 分别的数量
for(int i = 0; i < lengths;i++){
for(int j = 0; j < strs[i].length();j++){
if(strs[i].charAt(j) == '1') weight[i]++;
else value[i]++;
}
}
int [][] dp = new int [n + 1][m + 1];
for(int i = 0 ; i < lengths;i++){
for(int j = n ; j >= weight[i] ;j--){
for(int k = m ; k >= value[i]; k--){
dp[j][k] = Math.max(dp[j - weight[i]][k - value[i]] + 1,dp[j][k]);
}
}
}
return dp[n][m];
}
}
结尾:
本人第一次撰写博客文章,如有不足,还请海涵。