思路是把石头分两堆,两堆的重量尽量接近,即接近于 SUM / 2。 那么是01背包问题,取重量接近于SUM/2的子集。
class Solution {
public int lastStoneWeightII(int[] stones) {
if(stones.length == 1) {
return stones[0];
}
int sum = 0;
for(int i : stones) {
sum += i;
}
int[] dp = new int[15001];
dp[0] = 0;
int target = 0;
target = sum/2;
for(int i=0; i<stones.length; i++) {
for(int j=target; j>=stones[i]; j--) {
dp[j] = Math.max(dp[j], dp[j-stones[i]] + stones[i]);
}
}
return sum - dp[target] - dp[target];
}
}
把原数组分成两堆left和right,则有: left + right = sum left - right = target 可得: left = (sum+target) / 2 整数向下取整,那么(sum+target) % 2不为0时,无解。 target可为负,那么 abs(sum) < abs(target)时,无解。 可以只考虑正数,target为负时,取其绝对值。
以上,可以化为01背包问题。求和为(sum+target)/2的子集数目。 注意不是求最大子集和,而是求子集数。 递推公式为:dp[j] = dp[j] + dp[j-nums[i]]
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for(int i:nums) {
sum += i;
}
if(target < 0 && sum < -target) return 0;
if((target + sum) % 2 != 0) return 0;
int tar = (target + sum) / 2;
if(tar<0) tar = -tar;
int[] dp = new int[tar+1];
dp[0] = 1;
for(int i=0; i<nums.length; i++) {
for(int j=tar; j>=nums[i]; j--) {
dp[j] += dp[j-nums[i]];
}
}
return dp[tar];
}
}
- 确定dp数组(dp table)以及下标的含义
dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j] 。
- 确定递推公式
dp[i][j] 可以由前一个strs里的字符串推导出来,strs里的字符串有zeroNum个0,oneNum个1。
dp[i][j] 就可以是 dp[i - zeroNum][j - oneNum] + 1。
然后我们在遍历的过程中,取dp[i][j]的最大值。
所以递推公式: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])。
这就是一个典型的01背包! 只不过物品的重量有了两个维度而已。
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
int[][] dp = new int[m+1][n+1];
int oneNum = 0, zeroNum = 0;
for(String str : strs) {
oneNum = 0;
zeroNum = 0;
for(char c : str.toCharArray()) {
if(c == '0') {
zeroNum++;
}
if(c == '1') {
oneNum++;
}
}
for(int i=m; i>=zeroNum; i--) {
for(int j=n; j>=oneNum; j--) {
dp[i][j] = Math.max(dp[i][j], dp[i-zeroNum][j-oneNum] + 1);
}
}
}
return dp[m][n];
}
}