1049. 最后一块石头的重量 II
本题就和 昨天的 416.分割等和子集 很像了,可以尝试先自己思考做一做。
视频讲解:动态规划之背包问题,这个背包最多能装多少?LeetCode:1049.最后一块石头的重量II_哔哩哔哩_bilibili
有一堆石头,用整数数组 stones
表示。其中 stones[i]
表示第 i
块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x
和 y
,且 x <= y
。那么粉碎的可能结果如下:
- 如果
x == y
,那么两块石头都会被完全粉碎; - 如果
x != y
,那么重量为x
的石头将会完全粉碎,而重量为y
的石头新重量为y-x
。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量
,如果没有石头剩下,就返回 0
。
先分析题意,转化为数学问题
等价转化
设定 S1 和 S2
我们可以把所有石头的总重量 sum
看作两部分 S1
和 S2
:
S1
是某些石头组成的一部分重量。S2
是剩余石头组成的重量。- 目标是使
|S1 - S2|
尽可能小。
由于 S1 + S2 = sum
,那么最终结果可以转换为:
=> 这个问题就变成了:从石头中选出一些,使得总和尽可能接近 sum / 2
。
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
int sum=0;
for(int i=0;i<stones.size();i++)
{
sum+=stones[i];
}
int target=sum/2;
vector<int>dp(target+1,0);//背包能装的最大值问题,
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]);//表示一个容量为j的背包装石头,此时石头的体积和价值均为stone[i],所以可以套用经典公式
}
}
return sum-2*dp[target];
}
};
494. 目标和(组合总和问题)
大家重点理解 递推公式:dp[j] += dp[j - nums[i]],这个公式后面的提问 我们还会用到。
视频讲解:动态规划之背包问题,装满背包有多少种方法?| LeetCode:494.目标和_哔哩哔哩_bilibili
给你一个非负整数数组 nums
和一个整数 target
。
向数组中的每个整数前添加 '+'
或 '-'
,然后串联起所有整数,可以构造一个 表达式 :
- 例如,
nums = [2, 1]
,可以在2
之前添加'+'
,在1
之前添加'-'
,然后串联起来得到表达式"+2-1"
。
返回可以通过上述方法构造的、运算结果等于 target
的不同 表达式 的数目
解题思路:转化题意为熟悉的0-1背包模型
设:
left_sum
是选+
的数的总和right_sum
是选-
的数的总和total_sum = left_sum + right_sum
是nums
的总和- left_sum - right_sum = target left_sum + right_sum = sum
- left_sum = (sum + target) / 2
-
核心思想: 这个问题等价于:
从
nums
中选出若干个数,使得它们的和等于(sum + target) / 2
,求选法的总数。这就是一个0-1 背包问题!🎒
-
解题步骤
第一步:边界条件
(sum + target)
必须是偶数,否则无法拆分,返回0
。target
不能比sum
大,否则根本不可能凑出来。-
第二步:动态规划转化
- 设
dp[j]
表示 和为j
的不同子集数目。 - 初始状态:
dp[0] = 1
,因为选空集可以得到0
。 - 遍历
nums
,对于每个num
,我们从大到小更新dp[j]
:
这意味着如果dp[j] += dp[j - num];
dp[j - num]
之前有X
种方法能组成j - num
,那么加上num
后,这些方法也能组成j
。每加入一个数更新一次,更新nums.size()次,直到考虑到所有数都加入组合方式的总和
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int sum=0;
for(int num:nums)
{
sum+=num;
}
//将问题进行等价转化
int left_sum=(sum+target)/2;//数组中选择加号的数的绝对值总和
if((sum+target)%2!=0||abs(target)>sum)return 0;//
vector<int>dp(left_sum+1,0);//dp[n]表示从数组中选择和为n的数的可能性
dp[0]=1;//和为0,只有什么都不选这一种可能
for(int i=0;i<nums.size();i++)
{
for(int j=left_sum;j>=nums[i];j--)//逆序遍历,防止被重复选择
{
dp[j]+=dp[j-nums[i]];//
}
}
return dp[left_sum];
}
};
遍历物品放在外循环,遍历背包在内循环,且内循环倒序(为了保证物品只使用一次)。