代码随想录算法训练营第四十二天 | 1049.最后一块石头的重量II,494.目标和,474.一和零

1049.最后一块石头的重量II

题目讲解:代码随想录
重点:

  1. 如何把题目转换成01背包问题
  2. 理解背包容量的设定及如何返回

思路:

  1. dp数组的含义
// 背包容量为j能装的最大重量
int[] dp = new int[target + 1];
  1. 递推公式
// 取放石头i或者不放石头i的最大值
dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
  1. dp数组的初始化
// 全部初始化为0,避免后续取最大的更新被初始值覆盖
dp[j] = 0
  1. 遍历顺序
// 先正序遍历石头,再倒叙遍历背包容量
for (int i = 0; i < stones.length; i++)
   for (int j = target; j >= stones[i]; j--)
  1. 模拟dp数组

public int lastStoneWeightII(int[] stones) {
    // 求石头重量和及背包目标容量
    int sum = 0;
    for (int stone : stones) {
        sum += stone;
    }
    int target = sum >> 1;
    // 背包容量为j能装的最大重量
    int[] dp = new int[target + 1];
    // 先正序遍历石头,再倒叙遍历背包容量
    for (int i = 0; i < stones.length; i++) {
        for (int j = target; j >= stones[i]; j--) {
            // 取放石头i或者不放石头i的最大值
            dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
        }
    }
    // 返回特别需要注意
    // 因为target是除2向下取整,(sum - dp[target])肯定是大的那一堆石头
    return (sum - dp[target]) - dp[target];
}

494. 目标和

题目讲解:代码随想录
重点:

  1. 加法的总和为x,那么减法的总和就是(sum-x)。所以x - (sum-x) = target,x = (target + sum) / 2。此时问题就转化为,用nums装满容量为x的背包,有几种方法。注意是有几种方法。
  2. 背包问题求有几种组合方法的递推公式通常都有+=而不是取max
  3. 理解递推公式,不是求max而是加在一起了。
  4. 理解dp数组的含义。
二维数组解法

思路:

  1. dp数组的含义
// dp[i][j]为采用[0,i]的数字装满背包容量为j的背包有多少种方法
int[][] dp = new int[nums.length][x + 1];
  1. 递推公式
// 当前背包容量放不下数字i
if (nums[i] > j) {
   dp[i][j] = dp[i - 1][j];
} else {
   // 当前背包容量放得下数字i
   // 能够装满背包容量为j的背包分两种情况,一种放数字i,一种不放数字i
   dp[i][j] = dp[i - 1][j] + dp[i-1][j - nums[i]];
}
  1. dp数组的初始化
// 初始化第一行背包容量刚好为nums[0]能够装满有1种方法
if (x >= nums[0]) {
   dp[0][nums[0]] = 1;
}
// 初始化第一列背包容量为0,可以放数值为0的数字
int numZero = 0;
for (int i = 0; i < nums.length; i++) {
   if (nums[i] == 0) {
       numZero++;
   }
   dp[i][0] = (int)Math.pow(2, numZero);
}   
  1. 遍历顺序
// 先正序遍历物品,再正序遍历背包
for (int i = 1; i < nums.length; i++)
   for (int j = 1; j <= x; j++)
  1. 模拟dp数组

public int findTargetSumWays(int[] nums, int target) {
   // 计算nums的和及检查是否存在方法
   int sum = 0;
   for (int num : nums) {
       sum += num;
   }
   if (sum < Math.abs(target)) {
       return 0;
   }
   if ((sum + target) % 2 != 0) {
       return 0;
   }
   // 计算背包的最大容量
   int x = (sum + target) / 2;
   // dp[i][j]为用[0,i]的数字装满背包容量为j的背包有多少种方法
   int[][] dp = new int[nums.length][x + 1];
   // 初始化第一行背包容量刚好为nums[0]能够装满有1种方法
   if (x >= nums[0]) {
       dp[0][nums[0]] = 1;
   }
   // 初始化第一列背包容量为0,可以放数值为0的数字
   int numZero = 0;
   for (int i = 0; i < nums.length; i++) {
       if (nums[i] == 0) {
           numZero++;
       }
       dp[i][0] = (int)Math.pow(2, numZero);
   }
   // 先正序遍历物品,再正序遍历背包
   for (int i = 1; i < nums.length; i++) {
       for (int j = 1; j <= x; j++) {
           // 当前背包容量放不下数字i
           if (nums[i] > j) {
               dp[i][j] = dp[i - 1][j];
           } else {
               // 当前背包容量放得下数字i
               // 能够装满背包容量为j的背包分两种情况,一种放数字i,一种不放数字i
               dp[i][j] = dp[i - 1][j] + dp[i-1][j - nums[i]];
           }
       }
   }
   // 返回采用所有数字并且装满背包容量为x有多少种方法
   return dp[nums.length - 1][x];
}
一维数组解法

思路:

  1. dp数组的含义
// 装满背包容量为j的背包有多少种方法
int[] dp = new int[x + 1];
  1. 递推公式
// 不放数字i能装满的方法数 + 放数字i能装满的方法数
dp[j] += dp[j - nums[i]];
  1. dp数组的初始化
// 背包容量为0的时候有1种方法
dp[0] = 1;
  1. 遍历顺序
// 先正序遍历数字,再倒序遍历背包容量
for (int i = 0; i < nums.length; i++)
   for (int j = x; j >= nums[i]; j--)
  1. 模拟dp数组

public int findTargetSumWays(int[] nums, int target) {
   // 计算nums的和及检查是否存在方法
   int sum = 0;
   for (int num : nums) sum += num;
   if (sum < Math.abs(target)) return 0;
   if ((sum + target) % 2 != 0) return 0;
   // 计算背包的最大容量
   int x = (sum + target) / 2;
   // 装满背包容量为j的背包有多少种方法
   int[] dp = new int[x + 1];
   // 背包容量为0的时候有1种方法
   dp[0] = 1;
   // 先正序遍历数字,再倒序遍历背包容量
   for (int i = 0; i < nums.length; i++) {
       for (int j = x; j >= nums[i]; j--) {
           // 不放数字i能装满的方法数 + 放数字i能装满的方法数
           dp[j] += dp[j - nums[i]];
       }
   }
   // 返回背包容量为x的方法数
   return dp[x];
}

474. 一和零

题目讲解:代码随想录
重点:

  1. 理解strs就是物品,m和n是两个不同的背包。
  2. 理解dp数组的含义,递推公式,遍历顺序。

思路:

  1. dp数组的含义
// 最多有i个0和j个1的最大子集大小,i为0号背包的容量,j为1号背包的容量
int[][] dp = new int[m + 1][n + 1];
  1. 递推公式
// 不取当前str,取当前str
// 取当前str就需要减去当前str的0和1的个数
dp[i][j] = Math.max(dp[i][j], dp[i - zeroCount][j - oneCount] + 1);
  1. dp数组的初始化
// 保证不会后续更新不会被初始值覆盖即可
dp[i][j] = 0;
  1. 遍历顺序
// 先正序遍历strs,再倒序遍历两个背包,两个背包的先后顺序无所谓
for (int i = m; i >= zeroCount; i--)
   for (int j = n; j >= oneCount; j--)
  1. 模拟dp数组

public int findMaxForm(String[] strs, int m, int n) {
    // dp[i][j]: 最多有i个0和j个1的最大子集大小,i为0号背包的容量,j为1号背包的容量
    int[][] dp = new int[m + 1][n + 1];
    int zeroCount, oneCount;
    // 先遍历物品strs
    for (String str : strs) {
        zeroCount = 0;
        oneCount = 0;
        // 计算当前str的0和1的个数
        for (char ch : str.toCharArray()) {
            if (ch == '0') {
                zeroCount++;
            } else {
                oneCount++;
            }
        }
        // 再倒序遍历两个背包
        for (int i = m; i >= zeroCount; i--) {
            for (int j = n; j >= oneCount; j--) {
                // 不取当前str,取当前str
                dp[i][j] = Math.max(dp[i][j], dp[i - zeroCount][j - oneCount] + 1);
            }
        }
    }
    return dp[m][n];
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值