题目
有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
- 如果 x == y,那么两块石头都会被完全粉碎;
- 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块石头。返回此石头最小的可能重量 。如果没有石头剩下,就返回 0。
提示:
- 1 <= stones.length <= 30
- 1 <= stones[i] <= 100
题解
动态规划求解
和昨天的问题有点相似,昨天是在元素前赋上+、-号,求目标值,可以转化成在数组
n
u
m
s
nums
nums 中选取若干元素,使得这些元素之和等于
n
e
g
neg
neg。
今天可以将石头分成两拨,它们的差值就是最终的结果,因此差值尽可能要小。转化为背包问题就是,设总重量为sum, 有一个容量为sum/2的背包,在数组中取若干元素,使得背包装的越满。
这里使用滚动数组节省空间,所以要采取倒序遍历。若采取正序遍历,则在计算当前状态
d
p
[
j
]
dp[j]
dp[j]时,上一个状态的
d
p
[
j
−
s
t
o
n
e
[
i
]
]
dp[j-stone[i]]
dp[j−stone[i]]已经被覆盖掉了。因此倒序遍历,可以保证状态转移的一致性。
class Solution {
// 转换为0-1背包问题,石头总和为sum,每个背包容量为sum / 2,背包装的石头越接近容量越好
// 这样两个背包的差值越小,最后得到的结果越小
public int lastStoneWeightII(int[] stones) {
int sum = 0;
for(int stone : stones) sum += stone;
int[] dp = new int[sum / 2 + 1]; //dp[i]表示前i个石头,能装下的最大重量
for(int stone : stones){
for(int j = sum / 2; j >= stone; j--){ // 倒序遍历,保证计算中的仍是前一次的状态
dp[j] = Math.max(dp[j], dp[j - stone] + stone);
}
}
return sum - 2 * dp[sum / 2]; // 得到差值
}
}