最后一块石头重量问题
LC1049 最后一块石头的重量II及相关总结
最近总算是不忙了,做了几道题感觉还蛮有意思的,在此总结下。
先看下此题的基础版本:LC1046 最后一块石头的重量。
LC1046 最后一块石头的重量
1、 题目描述
有一堆石头,每块石头的重量都是正整数。
每一回合,从中选出两块最重的石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
- 如果 x == y,那么两块石头都会被完全粉碎;
- 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0。
2、题解
简单题,做起来还是没啥难度的,主要的关注点在于每次取最重的石头。可以直接使用一个最大堆来进行实现。每次取出最大堆的两个最大元素,将其差值重新加入到堆中,直到堆中的元素个数小于2个。
class Solution {
public int lastStoneWeight(int[] stones) {
PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> b - a);
for (int i = 0; i < stones.length; i++) {
pq.add(stones[i]);
}
while(pq.size() >= 2) {
Integer x = pq.poll();
Integer y = pq.poll();
if (!y.equals(x)) {
pq.add(x - y);
}
}
return pq.isEmpty() ? 0 : pq.poll();
}
}
当然这里可以自己手写下最大堆,增加点难度。
- 时间复杂度:O(n * logk) ,n是stones数组大小,k是堆的大小
- 空间复杂度:O(k)
LC1049 最后一块石头的重量II
1、题目描述
有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
- 如果 x == y,那么两块石头都会被完全粉碎;
- 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。
2、题解
此题相比于上面的第一道题,最大的不同便是可以选出任意两块石头,这也就意味着原来的贪心策略不再奏效。
但我们可以换个角度思考下,最后的答案其实就是所有的石头重量相互加减得到一个大于等于0的最小值。
因此,此题的思路可以转化一下,我们可以将所有的石头划分成两堆,并使得两个堆的差值最小。
由此,本问题其实可以变成一个01背包问题,我们取石头总重量的一半来作为我们背包的容量,我们要尽可能地去填满这个背包,这样差值就是最小。
class Solution {
public int lastStoneWeightII(int[] stones) {
int n = stones.length;
int sum = 0;
for (int i = 0; i < stones.length; i++) {
sum += stones[i];
}
//取石头总重量的一半来作为背包的容量
int cap = sum / 2;
int dp[] = new int[cap + 1];
for(int i = 0;i < n; i++){
int p = stones[i];
for(int j = cap;j >= p; j--){
dp[j] = Math.max(dp[j], dp[j - p] + p);
}
}
return sum - dp[cap] * 2;
}
}
- 时间复杂度:O(n * sum)。其中 n 是数组stones 的长度,sum 为stones 所有元素之和。
- 空间复杂度:O(sum)。
其他题目
之前做过一个其他题目,跟此题的思路一模一样。将这道题放在此处做个参考。
1、题目描述(NC509)
众所周知,牛能和牛可乐经常收到小粉丝们送来的礼物,每个礼物有特定的价值,他俩想要尽可能按照自己所得价值来平均分配所有礼物。
那么问题来了,在最优的情况下,他俩手中得到的礼物价值和的最小差值是多少呢?
p.s 礼物都很珍贵,所以不可以拆开算哦。
2、题解
思路一致,01背包,价值和容量相等问题。我们取礼物价值总和数的一半(向上取)来作为我们背包的容量。
public class NC509 {
public static int maxPresent (int[] presentVec) {
int n = presentVec.length;
if (n == 0) {
return 0;
}
int sum = 0;
for(int i = 0; i < n; i++){
sum += presentVec[i];
}
//简化为背包问题
int v = (sum + 1) / 2;
int[] dp = new int[v + 1];
for (int i = 0; i < n; i++) {
int p = presentVec[i];
for (int j = v; j >= p; j--) {
dp[j] = Math.max(dp[j], dp[j - p] + p);
}
}
return Math.abs(sum - 2 * dp[v]);
}
public static int maxPresent2 (int[] presentVec) {
int n = presentVec.length;
if (n == 0) {
return 0;
}
int sum = 0;
for(int i = 0; i < n; i++){
sum += presentVec[i];
}
//简化为背包问题
int v = (sum + 1) / 2;
int[][] dp = new int[n + 1][v + 1];
for (int i = 1; i <= n; i++) {
int p = presentVec[i - 1];
for (int j = 1; j <= v; j++) {
dp[i][j] = dp[i - 1][j];
if (j >= p) {
dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - p] + p);
}
}
}
return Math.abs(sum - 2 * dp[n][v]);
}
public static void main(String[] args) {
int[] nums = new int[]{41,467,334,1,169,224,478,358};
System.out.println(maxPresent2(nums));
}
}