算法训练营DAY36 第九章 动态规划part04
1049.最后一块石头的重量II
1049. 最后一块石头的重量 II - 力扣(LeetCode)
有一堆石头,每块石头的重量都是正整数。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
benti如果 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=sum/2的背包;遍历所有石头,找到最接近target的背包数量;那么剩余石头的重量就是sum-dp[target];答案输出的就是这两堆石头的差值。
1、确定dp数组及下标含义
dp[j]表示容量为j的背包最多能装的石头重量
2、确定递推公式
dp[j]=max(dp[j],dp[j-stones[i]]+stones[i])
3、初始化dp数组
这里石头肯定不会是负数,所以只需要全部初始化为0;
4、确定递推顺序
外层循环遍历物品(石头);内层循环遍历重量;内层循环需要倒序,从target开始--;
5、举例推理
class Solution {
public:int lastStoneWeightII(vector<int>& stones) {int sum=0;for(int i=0;i<stones.size();i++){sum+=stones[i];}int target;target=sum/2;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]);}}return sum-dp[target]-dp[target];}
};
494.目标和
494. 目标和 - 力扣(LeetCode)
给定一个非负整数数组,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。
思路:
把数组分为两部分x、y;根据题目条件我们知道x+y=sum;x-y=target;根据这两个方程,可以解出x、y;这里我们用x=(sum+target)/2;
回溯的思路也能解决问题,但是本题超时了;
此时问题就转化为,用nums装满容量为x的背包,有几种方法。
动态规划 (二维dp数组)
这次和之前遇到的背包问题不一样了,之前都是求容量为j的背包,最多能装多少。
本题则是装满有几种方法。其实这就是一个组合问题了。
1确定dp数组以及下标的含义
dp[i][j]:使用 下标为[0, i]的nums[i]能够凑满j(包括j)这么大容量的包,有dp[i][j]种方法。
2确定递推公式
本题中,物品i的容量是nums[i],价值也是nums[i]。
递推公式:dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]];
3dp数组如何初始化
先明确递推的方向,如图,求解 dp[2][2] 是由 上方和左上方推出。所以二维数组的最上行 和 最左列一定要初始化,这是递推公式推导的基础,
第一行
dp[0][j]:只放物品0, 把容量为j的背包填满有几种方法。
只有背包容量为 物品0 的容量的时候,方法为1,正好装满。
其他情况下,要不是装不满,要不是装不下。
所以初始化:dp[0][nums[0]] = 1 ,其他均为0 。
第一列
dp[i][0] : 背包容量为0, 放物品0 到 物品i,装满有几种方法。
都是有一种方法,就是放0件物品。
即 dp[i][0] = 1
但这里有例外,就是如果 物品数值就是0呢?其实就是算数组里有t个0,然后按照组合数量求,即 2^t 。
4确定遍历顺序
外层遍历nums;内层遍历背包,这里是二维dp数组,所以从上到下从左到右;
5举例推导dp数组
输入:nums: [1, 1, 1, 1, 1], target: 3
bagSize = (target + sum) / 2 = (3 + 5) / 2 = 4
dp数组状态变化如下:
class Solution {
public:int findTargetSumWays(vector<int>& nums, int target) {int sum =0;for(int i=0;i<nums.size();i++) sum+=nums[i];if(abs(target)>sum)return 0;if((target+sum)%2==1)return 0;int bagsize=(target+sum)/2;vector<vector<int>> dp(nums.size(),vector<int>(bagsize+1,0));if(nums[0]<=bagsize) dp[0][nums[0]]=1;dp[0][0]=1;int numzero=0;for(int i=0;i<nums.size();i++){if(nums[i]==0)numzero++;dp[i][0]=(int)pow(2.0,numzero);}for(int i=1;i<nums.size();i++){for(int j=0;j<=bagsize;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[nums.size()-1][bagsize];}
};
474.一和零
474. 一和零 - 力扣(LeetCode)
给你一个二进制字符串数组 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 。
1、确定dp数组与下标含义
dp[i][j]表示组成最多有i个0和j个1的最大子集大小
2、确定递推公式
dp[i][j] 可以由前一个strs里的字符串推导出来,strs里的字符串有zeroNum个0,oneNum个1。
dp[i][j] 就可以是 dp[i - zeroNum][j - oneNum] + 1。
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])。
3、dp数组如何初始化
因为物品价值不会是负数,初始为0,保证递推的时候dp[i][j]不会被初始值覆盖。
4、确定遍历顺序
外层便利物品也就是字符串;内层遍历背包(从后向前)
5、举例推导
class Solution {
public:int findMaxForm(vector<string>& strs, int m, int n) {vector<vector<int>> dp(m+1,vector<int>(n+1,0));for(string s:strs){int oneNum=0,zeroNum=0;for(char c:s){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];}
};