1049.最后一块石头的重量II
有一堆石头,每块石头的重量都是正整数。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块石头。返回此石头最小的可能重量。如果没有石头剩下,就返回 0。
示例:
- 输入:[2,7,4,1,8,1]
- 输出:1
解释:
- 组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
- 组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
- 组合 2 和 1,得到 1,所以数组转化为 [1,1,1],
- 组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。
分析:
本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小!------类似昨天分割等和子集的问题------++背包问题:找到一个子集,使其值尽量贴近target,但是小于等于target------原型是:找到一个物品的子集,使其value值尽量贴近包的负荷值,但小于等于包的负荷值++
++其实也相当于是,每个石头前可以填写正号或者负号,要求最后运算结果尽量贴近于0------类似于目标和++
类似昨天分割等和子集问题:dp[target]里是容量为target的背包所能背的最大重量。
最后的计算过程:
分成两堆石头,一堆石头的总重量是dp[target],另一堆就是sum - dp[target]。
在计算target的时候,target = sum / 2 因为是向下取整,所以sum - dp[target] 一定是大于等于dp[target]的。
那么相撞之后剩下的最小石头重量就是 (sum - dp[target]) - dp[target]。
cpp
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
int getSum(int *stones, int stoneSize) {
int sum = 0, i;
for (i = 0; i < stoneSize; ++i)
sum += stones[i];
return sum;
}
int lastStoneWeightII(int* stones, int stonesSize){
int sum = getSum(stones, stonesSize);
int target = sum / 2;
int i, j;
// 初始化dp数组
int *dp = (int*)malloc(sizeof(int) * (target + 1));
memset(dp, 0, sizeof(int) * (target + 1));
for (j = stones[0]; j <= target; ++j)
dp[j] = stones[0];
// 递推公式:dp[j] = max(dp[j], dp[j - stones[i]] + stones[i])
for (i = 1; i < stonesSize; ++i) {
for (j = target; j >= stones[i]; --j)
dp[j] = MAX(dp[j], dp[j - stones[i]] + stones[i]);
}
return sum - dp[target] - dp[target];
}
494.目标和
难度:中等
给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
示例:
- 输入:nums: [1, 1, 1, 1, 1], S: 3
- 输出:5
解释:
- -1+1+1+1+1 = 3
- +1-1+1+1+1 = 3
- +1+1-1+1+1 = 3
- +1+1+1-1+1 = 3
- +1+1+1+1-1 = 3
一共有5种方法让最终目标和为3。
分析:
为什么是0-1背包问题:给每一项前面添上+/-,可以看作2^n种可能性问题
------类似于找到两块子集,差为target------和石头问题是有一定相似程度的
target=(half+target)-half
并且要求2*half+target=total_sum
也就是前面填正号的部分之和=half+target
填负号的部分之和=half
------筛掉计算不出整数half的情况
**背包里存放的东西严格要求等于某个值
**需要计算多少种满足题意的方法
****不是太理解这个求和的思路!
**1、dp数组:**dp【j】填满j容量的包,最多有几种方法
2、递归关系: ++------有哪些来源可以推出dp[j]呢?++
eg:目标是装5,已经装了物品1, 还有dp[4]种方法可以装满
装满背包的方法 :dp[ j ] += dp[ j-nums[ i ] ] 对i累加
3、初始化
应当从递推公式出发>>>式子本来的物理意义
从递推公式可以看出,在初始化的时候dp[0] 一定要初始化为1,因为dp[0]是在公式中一切递推结果的起源,如果dp[0]是0的话,递推结果将都是0。
dp[j]其他下标对应的数值也应该初始化为0,从递推公式也可以看出,dp[j]要保证是0的初始值,才能正确的由dp[j - nums[i]]推导出来。------存在累加,必须全0初始化
4、顺序
第一层遍历物品
第二层遍历背包
或者换一种理解方式:用二维数组
**注意:数组赋值方式
int dp[numsSize+1][left+1];
memset(dp, 0, sizeof(dp));
用二维数组解决:更常规的思路!对于第i个元素,是放还是不放
cpp
int findTargetSumWays(int* nums, int numsSize, int target) {
int sum=0,left;
int i,j;
for(int i=0;i<numsSize;i++){
sum+=nums[i];
}
//排除比较特殊的情况
if((sum+target)%2 != 0 || abs(target) > abs(sum)) return 0;
left=(sum+target)/2;
//学习数组的赋值方式
int dp[numsSize+1][left+1];
memset(dp, 0, sizeof(dp));
dp[0][0]=1;
for(i=1;i<=numsSize;i++){
for(j=0;j<=left;j++){
//要不要放nums[i]
if(j<nums[i-1]) dp[i][j]=dp[i-1][j];
else dp[i][j]=dp[i-1][j]+dp[i-1][j-nums[i-1]];
}
}
return dp[numsSize][left];
}
一维数组+leetcode思路
cpp
int findTargetSumWays(int* nums, int numsSize, int target) {
int sum=0,left;
int i,j;
for(int i=0;i<numsSize;i++){
sum+=nums[i];
}
if((sum+target)%2 != 0 || abs(target) > abs(sum)) return 0;
left=(sum+target)/2;
int k[left+1];
memset(k, 0, sizeof(k));
k[0]=1;
for (i=1;i<=numsSize;i++){
for (j=left;j>=0;j--){
if(j>=nums[i-1]) k[j]=k[j]+k[j-nums[i-1]];
}
}
return k[left];
}
474.一和零
给你一个二进制字符串数组 strs 和两个整数 m 和 n 。
请你找出并返回 strs 的最大子集的大小,该子集中 最多 有 m 个 0 和 n 个 1 。
如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。
示例 1:
-
输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
-
输出:4
-
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。 其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。
示例 2:
- 输入:strs = ["10", "0", "1"], m = 1, n = 1
- 输出:2
- 解释:最大的子集是 {"0", "1"} ,所以答案是 2 。
分析:
确定dp数组(dp table)以及下标的含义:
dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。
------其实变成三维的问题了,也就是背包的重量有两个维度,物品有一个维度
但是把物品的维度变成滚动数组------所以还是两个维度
确定递推公式
dp[i][j] 可以由前一个strs里的字符串推导出来,strs里的字符串有zeroNum个0,oneNum个1。
dp[i][j] 就可以是 dp[i - zeroNum][j - oneNum] + 1。
然后我们在遍历的过程中,取dp[i][j]的最大值。
所以递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
此时大家可以回想一下01背包的递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
对比一下就会发现,字符串的zeroNum和oneNum相当于物品的重量(weight[i]),字符串本身的个数相当于物品的价值(value[i])。
dp数组如何初始化
因为物品价值不会是负数,01背包的dp数组初始化为0就可以。
确定遍历顺序
01背包+滚动数组:外层for循环遍历物品,内层for循环遍历背包容量且从后向前遍历!
cpp
int findMaxForm(char** strs, int strsSize, int m, int n) {
int dp[m+1][n+1];
memset(dp, 0, sizeof(dp));
int i,j,object;
for (object=0;object<strsSize;object++){
char *x=strs[object];
int n1=0,n0=0;
for (int k=0;k<strlen(x);k++){
if(x[k]=='0') n0++;
else n1++;
}
for(i=m;i>=0;i--){//背包维度都需要逆序哈!
for(j=n;j>=0;j--){
if (i>=n0 && j>=n1) dp[i][j]=fmax(dp[i][j], dp[i-n0][j-n1]+1);
}
}
}
return dp[m][n];
}