先说一下01背包问题的特点:
首先就是该背包中的物品有且只有一项,也就意味着无法重复往背包里加同一个物品
dp数组的下标表示容量,值表示最终的重量
例题1:1049最后一块石头的重量II
做着一类题最先需要找到的是最大容量是多少?
本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了。
那么容量就是Math.floor(sum / 2)
那么接下来就是直接套公式就行了
/**
* @param {number[]} stones
* @return {number}
*/
var lastStoneWeightII = function (stones) {
let sum = stones.reduce((s, n) => s + n);
let dpLen = Math.floor(sum / 2);
let dp = new Array(dpLen + 1).fill(0);
for (let i = 0; i < stones.length; ++i) {
for (let j = dpLen; j >= stones[i]; --j) {
dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
}
}
return sum - dp[dpLen] - dp[dpLen];
};
例题2:494目标和
本题个人认为是一道hard的题目,作为中等题应该是因为题意比较好懂
同样也是先找容量代表谁,如果用left表示带+的数量,right反之,就会有如下公式:
left + right = sum
left-right=target
联立不难得出
left = (target + sum)/2
left就是最后的容量
求完容量就来到了本题的第二个难点:
如何递推,根据题意的要求是求指定容量下组合方式数量,那就是累加
dp[j] += dp[j - nums[i]]
接下来套公式就行了
完整代码:
const findTargetSumWays = (nums, target) => {
const sum = nums.reduce((a, b) => a+b);
if(Math.abs(target) > sum) {
return 0;
}
if((target + sum) % 2) {
return 0;
}
const halfSum = (target + sum) / 2;
let dp = new Array(halfSum+1).fill(0);
dp[0] = 1;
for(let i = 0; i < nums.length; i++) {
for(let j = halfSum; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[halfSum];
};
注意一些剪枝操作避免出现报错
例题3:474一和零
这一题的关键在于容量有两个但是并不难找
递推公式的关键是加一
完整代码:
const findMaxForm = (strs, m, n) => {
const dp = Array.from(Array(m+1), () => Array(n+1).fill(0));
let numOfZeros, numOfOnes;
for(let str of strs) {
numOfZeros = 0;
numOfOnes = 0;
for(let c of str) {
if (c === '0') {
numOfZeros++;
} else {
numOfOnes++;
}
}
for(let i = m; i >= numOfZeros; i--) {
for(let j = n; j >= numOfOnes; j--) {
dp[i][j] = Math.max(dp[i][j], dp[i - numOfZeros][j - numOfOnes] + 1);
}
}
}
return dp[m][n];
};