最后一块石头的重量II
若要让剩余的石头重量最小,只需要将石头分成重量近似的两堆。因此我们将石头总重量的一半作为背包容量,希望装进来的石头能尽量将背包填满。这就转化成了分割等和子集那道题的思想。
class Solution{
public:int lastStoneWeightII(vector<int>& stones) {int sum = 0;for(int weight : stones) {sum += weight;}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]);}}// target是向下取整,所以sum-dp[target]一定比dp[target]大,且二者尽量接近return sum - dp[target] - dp[target];}
};
目标和
原理上不难理解,target 是在数组中添加符号之后的结果,那么 (sum + target) / 2 就是将负号的元素抵消掉之后得到的正数之和。仍然可以转化为01背包问题,从数组中挑选数字使之加和等于 (sum + target) / 2,每个数字只能使用一次。
但有一些小问题需要注意,这道题中给的数组中的数字都是大于等于0的。所以如果加和小于target 的绝对值,证明所有都是正数或者所有数字添加负号都不能达到 target,这时直接返回0。另外sum + target一定是一个偶数,不然也没法添加负号得到 target,这种情况也要返回0。
class Solution {
public:int findTargetSumWays(vector<int>& nums, int target) {int sum = 0;for(int num : nums) {sum += num;}if(sum < abs(target)) return 0;if((sum + target) % 2) return 0;int bagSize = (sum + target) / 2;vector<int> dp(bagSize + 1, 0); // dp[i]表示背包容量为i时数字的选择方式数量dp[0] = 1; // 当背包容量为0时,只有都不选择这一种方式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];}
};
一和零
这道题就是从两个维度限制了背包大小,1的数量和0的数量。另外它要找的是字符串数目最多的子集,所以每个字符串的价值就是1,价值最大那么字符串数目就越多。
class Solution{
public:int findMaxForm(vector<string>& strs, int m, int n) {// 01两个维度来限制背包大小vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));for(string str : strs){int ones = 0;int zeros = 0;for(char ch : str) {if(ch == '1') ones++;else zeros++;}for(int i = m; i >= zeros; i--) {for(int j = n; j >= ones; j--) {dp[i][j] = max(dp[i][j], dp[i - zeros][j - ones] + 1);}}}return dp[m][n];}
};