0
点赞
收藏
分享

微信扫一扫

LeetCode刷题day54


文章目录

  • ​​416. 分割等和子集​​
  • ​​题目描述​​
  • ​​思路分析​​
  • ​​参考代码​​
  • ​​1049. 最后一块石头的重量 II​​
  • ​​题目描述​​
  • ​​思路分析​​
  • ​​参考代码​​

416. 分割等和子集

​​题目描述​​

给你一个 只包含正整数非空 数组 ​​nums​​ 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:

输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] [11]

示例 2:

输入:nums = [1,2,3,5]
输出:false

思路分析

本题要求集合里能否出现总和为 sum / 2 的子集。

只有确定了如下四点,才能把01背包问题套到本题上来。

  • 背包的体积为sum / 2
  • 背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
  • 背包如果正好装满,说明找到了总和为 sum / 2 的子集。
  • 背包中每一个元素是不可重复放入。

动规五部曲

  1. 确定dp数组以及下标含义

01背包中,dp[j]表示:容量为j的背包,所背的物品最大价值为dp[j].

回到本题,dp[j]表示:背包总容量为j,最大可以凑成j的子集总和为dp[j]

  1. 确定递推公式

本题中,背包中放入数值,那么物品i的重量是nums[i],其价值也是nums[i]

所以递归公式: ​​dp[j] = max(dp[j],dp[j-nums[i]]+nums[i])​

3.dp数组如何初始化

当背包容量为0时,放入数值的总和为0,所以 ​​dp[0] = 0​

  1. 确定遍历顺序

根据卡尔大佬的 ​​动态规划:关于01背包问题,你该了解这些!(滚动数组)​​ 的讲解,如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历.

  1. 举例推导dp数组

LeetCode刷题day54_数组

参考代码

#include<bits/stdc++.h>
using namespace std;

bool canPartition(vector<int>& nums) {
int sum = 0;
for(int i = 0; i < nums.size(); i++) {
sum+=nums[i];
}
if(sum % 2 == 1) { //如果之和是奇数,则无法分割成两个相同的子集.
return false;
}
int target = sum / 2;
vector<int> dp(10001,0);//定义dp
dp[0] = 0;//初始化dp[0]
for(int i = 0; i < nums.size(); i++) { //遍历集合所有数
for(int j = target; j >=nums[i]; j--) { //遍历背包大小. 只有背包体积>=num[i],当前价值才可能更新,否则和上一个一样.
dp[j] = max(dp[j],dp[j-nums[i]] + nums[i]);
}
}
if(dp[target]==target) { //如果背包可以放下target的数,则说明可以分割.
return true;
}
return false;
}

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

​​题目描述​​

有一堆石头,用整数数组 ​​stones​​​ 表示。其中 ​​stones[i]​​​ 表示第 ​​i​​ 块石头的重量。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 ​​x​​​ 和 ​​y​​​,且 ​​x <= y​​。那么粉碎的可能结果如下:

  • 如果​​x == y​​,那么两块石头都会被完全粉碎;
  • 如果​​x != y​​​,那么重量为​​x​​​ 的石头将会完全粉碎,而重量为​​y​​​ 的石头新重量为​​y-x​​。

最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 ​​0​​。

示例 1:

输入:stones = [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],这就是最优值。

示例 2:

输入:stones = [31,26,33,21,40]
输出:5

示例 3:

输入:stones = [1,2]
输出:1

思路分析

本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小这样就化解成01背包问题了

这就和上面的 416. 分割等和子集 (opens new window) 非常像了。

动归五部曲

  1. 确定dp数组以及下标含义

dp[j]表示容量为j的背包,最多可以背dp[j]这么重的石头

  1. 确定递推公式

本题和上题类似,上题是子集一半,本题是一半石头. 递推公式: ​​dp[j] = max(dp[j],dp[j-nums[i]] + nums[i]);​

  1. dp数组如何初始化

提示中给出1 <= stones.length <= 30,1 <= stones[i] <= 1000,所以最大重量就是30 * 1000 。

而我们要求的target其实只是最大重量的一半,所以dp数组开到15000+1大小就可以了。

然后是dp初始化,因为重量都是 > 0,所以​​dp[0] = 0​

  1. 确定遍历顺序

根据卡尔大佬的 ​​动态规划:关于01背包问题,你该了解这些!(滚动数组)​​ 的讲解,如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历.

  1. 举例推导dp数组

以两个例子演示dp数组的推导过程,如下:

LeetCode刷题day54_for循环_02

参考代码

#include<bits/stdc++.h>
using namespace std;

//打印dp数组
void print(vector<int>& dp,int& num,int& target ){
cout<<"dp["<<num<<"]: ";
for(int i = 0; i <= target;i++){
cout<<dp[i]<<" ";
}
cout<<endl;
}
int lastStoneWeightII(vector<int>& stones) {
int sum = 0;
for(int i = 0; i < stones.size(); i++) {
sum+=stones[i];
}
int target = sum / 2;
// cout<<"石头之和为:"<<sum<<endl;
vector<int> dp(15001,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]) ;
}
// print(dp,i,target); //打印输出
}

return sum-dp[target]-dp[target];
}

int main(){
// vector<int> stones = {2,7,4,1,8,1};
vector<int> stones = {2,4,1,1};
lastStoneWeightII(stones);
return 0;
}

举报

相关推荐

0 条评论