1049. 最后一块石头的重量 II
和 LC 416.分割等和子集 类似
-
思路(我没有思路):
两块石头相撞,这里没有想到的一个点是,相撞的两个石头要几乎相似
以示例1为例,stones = [2,7,4,1,8,1],如果从左到右相撞,得到的不是最优结果
最优结果是相撞后重量最小的stones = [2,7,4,1,8,1],总和23,一半为11,把这组石头分为一组11 一组12,他们相撞就是1
0-1背包,M个物品放入容量为N的背包中,物品价值value、重量weight
递归五步:
- 确定dp数组(dp table)以及下标的含义
背包重量j
最大价值dp[j]
最大重量dp[j]
- 确定递推公式
价值:dp[j] = Math.max(dp[j], dp[j-weight[i]] +value[i])
重量:dp[j] = Math.max(dp[j], dp[j-stones[i]] +stones[i])
- dp数组如何初始化
①dp[0] = 0
,其余也都是0
② 由于题目定义1 <= stones.length <= 30; 1 <= stones[i] <= 100
所以极端情况下每个元素都为100,共30个元素,sum = 3000 , half = 1500
int[]dp = new int [1501]
- 确定遍历顺序
for物品 { for背包 } - 举例推导dp数组
求得一组石头的重量是 a = dp[target] ,另一组为 b= sum-dp[target]
这里 b-a一定大于零,因为sum/2是向下取整的
- ac-二维数组版本
写的时候出错:① 递推公式写错 ② 初始化行列不分 ③ 初始化的值应为固定的value[0]
这道题目的二维数据版本空间内存消耗很多。
class Solution {
public int lastStoneWeightII(int[] stones) {
int sum = 0;
for(int i=0; i<stones.length; i++) {
sum += stones[i];
}
int bagSize = sum/2; // N
// M = stones.length
int[][] dp = new int[stones.length][bagSize+1]; //二维数组空间多很多
//初始化第0行,物品0从哪里开始放
for(int i=stones[0]; i<bagSize+1; i++) { // weight[0]
dp[0][i] = stones[0];//value[0]错了
}
for(int i=1; i<stones.length; i++) {
for(int j=1; j<bagSize+1; j++) {
if(j<stones[i]) {
dp[i][j] = dp[i-1][j];
} else {
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-stones[i]] + stones[i] );
}
}
}
return sum-2*dp[stones.length-1][bagSize];
}
}
- ac-滚动数组版本
写的过程中:递推公式又错了。
class Solution {
public int lastStoneWeightII(int[] stones) {
int sum = 0;
for(int i=0; i<stones.length; i++) {
sum += stones[i];
}
int bagSize = sum/2; // N
int M = stones.length; // M
int[] dp = new int[bagSize+1];
for(int i=0; i<M; i++) {
for(int j=bagSize; j>=stones[i]; j--) { // weight[i]
//两种情况,要么放,要么不放
dp[j] = Math.max(dp[j],dp[j-stones[i]]+stones[i]); // 1 weight 2 value
}
}
return sum-2*dp[bagSize];
}
}
494. 目标和
思路
准备两个背包,一个背包package_a
存放标记为正的元素,另一个背包package_b
存放标记为负的元素。package_a - package_b = target
。
设nums的元素和为sum, 可以列出方程:
package_a - package_b = target;
package_a + package_b = sum;
所以 package_a = (target + sum)/2
。 所以根据题意给的target和sum,我们可以求出package_a的值。
那这道题就可以转化为:给定一个大小为package_a的背包,有多少种组合方式能把背包装满? 妥妥的0-1背包。
元素放或者不放
答案
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for(int i=0; i<nums.length; i++) {
sum += nums[i];
}
if(sum < Math.abs(target)){
return 0;
}
if((sum + target) % 2 != 0) {
return 0;
}
int bagSize = (target+sum)/2;
int M = nums.length;
int[][]dp = new int[M][bagSize+1];
//首行-初始化
if(nums[0] <= bagSize) {
dp[0][nums[0]] = 1;
}
//首列-初始化
int zeroNum = 0;
for(int i=0; i<M; i++) {
if(nums[i] == 0) {
zeroNum++;
}
dp[i][0] = (int) Math.pow(2,zeroNum);
// 有0的话,选或不选 2的x次方
// 没有0,首列全部设为1??什么意思
}
for(int i=1; i<M; i++) {
for(int j=1; j<bagSize+1; j++) {
if(nums[i]>j) {
dp[i][j] = dp[i-1][j];
} else {
dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]];
}
}
}
return dp[M-1][bagSize];
}
}
难点
(1)错误状况 return 0
的情况
//如果target的绝对值大于sum,那么是没有方案的
if (Math.abs(target) > sum) return 0;
//如果(target+sum)除以2的余数不为0,也是没有方案的
if ((target + sum) % 2 == 1) return 0;
(2)递推公式 dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]]
的由来
(3)数组的初始化
- 首行初始化:只初始化二维数组M*N里的一个格子,就是 j=nums[0] 时,也就是 只选 物品0,其余都不选,此时情况赋为1。
if(nums[0] <= bagSize) {
dp[0][nums[0]] = 1;
}
- 首列初始化:
(1)数组元素有0的话,选或不选 2的x次方
(2)数组元素没有0,首列全部设为1??什么意思
dp[i][0] = 1,背包容量为0的时候,情况为1,或许可以理解为 这题本来就不是放背包,是求和??
看到别人的解释:任何一个物品,只要选择不取的方案,就能凑成0容量的背包
int zeroNum = 0;
for(int i=0; i<M; i++) {
if(nums[i] == 0) {
zeroNum++;
}
dp[i][0] = (int) Math.pow(2,zeroNum);
// (1)数组元素有0的话,选或不选 2的x次方
// (2)数组元素没有0,首列全部设为1??什么意思
// dp[i][0] = 1,背包容量为
}
出错点
① Math.pow(); 之前要写 (int) 强制类型转换
Line 32: error: incompatible types: possible lossy conversion from double to int
dp[i][0] = Math.pow(2,zeroNum);
打印dp(为了方便理解)
(1)
int[]nums = {1,1,1,1,1};
int target = 3;
- 初始化
- 最终dp
(2)打印
int[]nums = {0,1,0,1,1};
int target = 1;
- 初始化
- 最终dp
474.一和零
我的思路
这道题放在代码随想录的0-1背包分类下,怎么抽象为背包问题?
重量满足两个维度 m n,同时尽可能是最大长度,三维背包?
int dp[strs.length][m+1][n+1]
dp[k][i][j]: 从0-k物品(strs[])中任选,拥有最大的子集长度(i表示0长度,j表示1长度)
1. 递推公式(假设和0-1推导方法相同)
(1)【不取】假设取物品k,但超过了容量(i,j)的限制,所以不放k
if(strs[k]的0 1数量 > m n) 这里任意一个大于都不可以
dp[k][i][j] = dp[k-1][i][j]
(2)【取】
A. 能放但是不放 dp[k][i][j] = dp[k-1][i][j]
B. 能放且放了 dp[k][i][j] = dp[k-1][i-weight0[k]][j-weight1[k]]+value[k]
weight0[k]是字符串k拥有的0的长度 ,weight1[k]是字符串k拥有的1的长度,value[k]是字符串k的长度
总和 dp[k][i][j] = Math.max(dp[k-1][i][j] , dp[k-1][i-weight0[k]][j-weight1[k]]+value[k])
2. 初始化dp数组
三维数组抽象为一个立方体
(1) dp[0][i][j] 相当于顶面,这一面表示物品0,放进容量为(i,j)的背包里,拥有的最大子集长度
for(int t1=weight0[0]; t1<m+1; t1++ ) {
for(int t2=weight1[0]; t2<n+1; t2++) {
dp[0][i][j] = value[0]
}
}
(2) dp[k][0][j] 首面,这一面表示物品k,放进容量为(0,j)的背包里,拥有的最大子集的长度,
for(int a=0; a<strs.length; a++ ) {
for(int t=0; t<n+1; t++) {
if(weight1[a] <= t) {
dp[a][0][t] = value[a]
}
}
}
(3)dp[k][i][0]左面,这一面表示物品k,放进容量为(i,0)的背包里,拥有的最大子集的长度,
for(int a=0; a<strs.length; a++ ) {
for(int t=0; t<m+1; t++) {
if(weight0[a] <= t) {
dp[a][t][0] = value[a]
}
}
}
3.遍历顺序
for(int a=0; a<strs.length; a++) {
for(int t1=0; t1<m+1; t1++) {
for(int t2=0; t2<n+1; t2++) {
dp[a][t1][t2] =
}
}
}
- 我的想法错误点:value表示的子集的多少,不是字符的长度。初始化想的很复杂
正确版本
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
int len = strs.length;
int[][][]dp = new int [len + 1][m + 1][n + 1];
for(int i=1; i<len+1; i++) {
int[]zeroOnes = getZeroOne(strs[i - 1]);
int zero=zeroOnes[0], one=zeroOnes[1];
for(int j=0; j<m+1; j++) {
for(int k=0; k<n+1; k++) {
dp[i][j][k] = dp[i - 1][j][k];
if(zero <= j && one <= k) {
dp[i][j][k] = Math.max(dp[i - 1][j][k], dp[i - 1][j - zero][k - one]+1);
}
}
}
}
return dp[len][m][n];
}
public int[] getZeroOne(String s) {
int[] num = new int[2];
for(int i=0; i<s.length(); i++) {
if(s.charAt(i) == '0') {
num[0]++;
} else {
num[1]++;
}
}
return num;
}
}
错误点
这数组初始化在哪?
为什么for j=0 k=0从0开始?
java
-
除以 2 :
int target = sum >> 1;
-
强制类型转换:int x = (int)Math.pow(a, b); 意思是 a的b次方
public static double pow(double 基数,double 幂次)