Day43最后石头的重量、目标和、一和零
By HQWQF 2024/01/31
笔记
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],这就是最优值。
提示:
- 1 <= stones.length <= 30
- 1 <= stones[i] <= 1000
动态规划
只要我们尽量把让石头分成重量相同的两堆,这样相撞之后剩下的石头就会最小。
得出容量为sum/2的背包能装下的石头的最大总重量后,让Sum减去这个总重量的两倍就行。
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
vector<int> dp(15001, 0);
int sum = 0;
for (int i = 0; i < stones.size(); i++) sum += stones[i];
int target = sum / 2;
for (int i = 0; i < stones.size(); i++) { // 遍历物品
for (int 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。
提示:
- 数组非空,且长度不会超过 20 。
- 初始的数组的和不会超过 1000 。
- 保证返回的最终结果能被 32 位整数存下。
动态规划
若要让表达式结果为target,表达式中的一部分元素的符号需要是+一部分是-,设符号为+的部分元素的和为left,符号为-的部分元素的和为right,那么有 left - right = target。
同时我们有left + right = sum,解方程可得 left = (target + sum)/2 。要求添加符号的方法数就是求left 的组合,即问题转化为了在数组中找元素组合为(target + sum)/2 ,求组合的数量。
首先如果(target + sum) / 2 不为整说明没有组合,直接返回0。
先设一维dp表,dp[j]代表填满j(包括j)这么大容积的包,有dp[j]种方法
和前面一样,我们需要先遍历所有元素(遍历每行),每一行都代表使用num[0]、num[1]、……num[i],这些元素。对于每一行的dp[j],相比上一行,我们多了nums[i]可以使用,如果不使用nums[i]就和之前的组合重复所以不考虑。如果使用了nums[i],那么dp[j] (新使用nums[i])就等于未使用nums[i]凑到j - nums[i]的方法数,也就是上一行(上一迭代)的dp[j - nums[i]],考虑使用所有nums元素的情况,把这些dp[j](新使用num某个元素)累加就是dp[j]。
即dp[j] += dp[j - nums[i]]。
例如:dp[j],j 为5,
- 已经有一个1(nums[i]) 的话,有 dp[4]种方法 凑成 容量为5的背包。
- 已经有一个2(nums[i]) 的话,有 dp[3]种方法 凑成 容量为5的背包。
- 已经有一个3(nums[i]) 的话,有 dp[2]中方法 凑成 容量为5的背包
- 已经有一个4(nums[i]) 的话,有 dp[1]中方法 凑成 容量为5的背包
- 已经有一个5 (nums[i])的话,有 dp[0]中方法 凑成 容量为5的背包
初始化方面dp[0] = 1;其余为0。
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
int sum = 0;
for (int i = 0; i < nums.size(); i++) sum += nums[i];
if (abs(S) > sum) return 0; // 此时没有方案
if ((S + sum) % 2 == 1) return 0; // 此时没有方案
int bagSize = (S + sum) / 2;
vector<int> dp(bagSize + 1, 0);
dp[0] = 1;
for (int i = 0; i < nums.size(); i++) {
for (int j = bagSize; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[bagSize];
}
};
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 。
提示:
- 1 <= strs.length <= 600
- 1 <= strs[i].length <= 100
- strs[i] 仅由 '0' 和 '1' 组成
- 1 <= m, n <= 100
动态规划
dp表为二维,dp[i][j]代表最多有i个0和j个1的(strs子集)的最大大小为dp[i][j]。
dp表为滚动,遍历每一个元素都遍历一遍dp表,逐步更新dp[i][j]的最大值,所以注意从后往前遍历。
zeroNum和oneNum是strs的一个元素中的0和1的数量。
递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
如果最多有i个0和j个1的(strs子集)有当前元素的话更加大,那么这个更大的子集去掉这个元素后的(strs子集)也一定是(最多有i - zeroNum个0和j - oneNum个1)的(strs子集)中最大的,这个去掉后的子集大小加1也就是当前元素就等于在这个假设下的更大的子集的大小。
初始值方面,在只有一个元素的时候,没有这个元素的子集大小为0,所以全部初始为0。
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {
vector<vector<int>> dp(m + 1, vector<int> (n + 1, 0)); // 默认初始化0
for (string str : strs) { // 遍历物品
int oneNum = 0, zeroNum = 0;
for (char c : str) {
if (c == '0') zeroNum++;
else oneNum++;
}
for (int i = m; i >= zeroNum; i--) { // 遍历背包容量且从后向前遍历!
for (int j = n; j >= oneNum; j--) {
dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
}
}
}
return dp[m][n];
}
};
标签:nums,int,重量,strs,算法,子集,Day43,sum,dp
From: https://www.cnblogs.com/HQWQF/p/17998578