本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了。
和416. 分割等和子集非常像,只需要使用石头总重量的一半作为背包容量,通过01背包的dp算法,计算出一半容量所能装入的最大石头重量,就能使得最后剩余的石头最小
本题物品的重量为stones[i],物品的价值也为stones[i]
先写出常见的二维数组解法:
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
if(stones.size() == 1){
return stones[0];
}
int n = stones.size();
int sum = accumulate(stones.begin(), stones.end(), 0);
int content = sum / 2;
vector<vector<int>> dp(n, vector<int>(content + 1, 0)); // 由于j是容量,不仅是下标,还有实际意义,需要content+1
for(int j = 1; j <= content; j++){
dp[0][j] = j >= stones[0] ? stones[0] : 0;
}
// 先遍历物品,再遍历容量
for(int i = 1; i < n; i++){
for(int j = 1; j <= content; j++){
if(j < stones[i]){
// 当前背包容量 < 物品重量
dp[i][j] = dp[i-1][j];
}else{
dp[i][j] = max(dp[i-1][j], dp[i-1][j-stones[i]] + stones[i]);
}
}
}
return sum - 2 * dp[n-1][content];
}
};
通过滚动数组优化,尤其需要注意:
- 为什么需要从后往前遍历容量
- 为什么遍历容量时,没有显式判断当前容量j小于当前物品重量nums[i]
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
if(stones.size() == 1){
return stones[0];
}
int n = stones.size();
int sum = accumulate(stones.begin(), stones.end(), 0);
int content = sum / 2;
// vector<vector<int>> dp(n, vector<int>(content + 1, 0)); // 由于j是容量,不仅是下标,还有实际意义,需要content+1
vector<int> dp(content + 1, 0);
// 用第一个物品初始化dp数组
for(int j = 1; j <= content; j++){
dp[j] = j >= stones[0] ? stones[0] : 0;
}
// 先遍历物品,再遍历容量
for(int i = 1; i < n; i++){
// 滚动数组,从后往前遍历容量
for(int j = content; j >= stones[i]; j--){
dp[j] = max(dp[j], dp[j-stones[i]] + stones[i]);
}
}
return sum - 2 * dp[content];
}
};