给你一个披萨,它由 3n 块不同大小的部分组成,现在你和你的朋友们需要按照如下规则来分披萨:
- 你挑选 任意 一块披萨。
- Alice 将会挑选你所选择的披萨逆时针方向的下一块披萨。
- Bob 将会挑选你所选择的披萨顺时针方向的下一块披萨。
- 重复上述过程直到没有披萨剩下。
每一块披萨的大小按顺时针方向由循环数组 slices
表示。
请你返回你可以获得的披萨大小总和的最大值。
输入:slices = [1,2,3,4,5,6]
输出:10
解释:选择大小为 4 的披萨,Alice 和 Bob 分别挑选大小为 3 和 5 的披萨。然后你选择大小为 6 的披萨,Alice 和 Bob 分别挑选大小为 2 和 1 的披萨。你获得的披萨总大小为 4 + 6 = 10 。
输入:slices = [8,9,8,6,1,1]
输出:16
解释:两轮都选大小为 8 的披萨。如果你选择大小为 9 的披萨,你的朋友们就会选择大小为 8 的披萨,这种情况下你的总和不是最大的。
提示:
1 <= slices.length <= 500
slices.length % 3 == 0
1 <= slices[i] <= 1000
思路:
首先,每一次选择都是可以自由选择披萨,但是选择完成之后,左右两边披萨则是不能选择,所以可以简化题目,看成在循环列表中,选取n/3个不连续的元素的最大值。
两种解法:1、类似于小偷偷家的动态规划 2、贪心+优先队列模拟取数
这里只介绍第2种方法(第1种有空补上。。)
这道题目中,直观想到的贪心策略是每一步选取最大的一块。但以[8,9,8,1,2,3]为例,如果我们第一步选取了9,剩下的元素就变成了[1,2,3],我们最大只能选择3,这样的总和就只有12,而显然选取两个8可以得到16的总和,是更优的。
如果我们可以反悔就好了。问题是,怎么反悔?在上面的例子中,我们第一步选9之后,如果直接删除两个8,那就失去了反悔的机会,因为后面再也不会处理到它们了。所以,我们需要删除两个8对应的节点,同时保留它们的信息。信息保留在哪里?只能是9所对应的节点。
我们在选取9之后,将左右两个节点删除 ,同时将9修改为8+8−9=7,这样我们后面仍然有机会选到这个7,也就相当于反悔了对9的选择,而去选择了左右两边的两个8。
重复这样的操作,直到选取了n/3个元素为止,我们就得到了需要的最优解。
为什么我们的反悔操作一定是同时选择左右两个元素呢?因为我们是从大到小处理所有元素的,所以左右两边的元素一定不大于中间的元素,如果我们只选取其中的一个,是不可能得到更优解的。
ac code O(nlogn):
java
import java.util.Comparator;
import java.util.PriorityQueue;
public class Node<T> {
public T data;
public int index;
public Node<T> pre;
public Node<T> next;
public Node(){}
public Node(T data) {this.data = data;}
}
class Solution {
public int maxSizeSlices(int[] slices) {
PriorityQueue<Node<Integer>> pq = new PriorityQueue<>(new Comparator<Node<Integer>>() {
@Override
public int compare(Node<Integer> o1, Node<Integer> o2) {
return o2.data - o1.data; // 从大到小进行排序
}
});
int n = slices.length;
Node[] nodes = new Node[n];
int step = 0;
int maxStep = n / 3;
int ans = 0;
boolean[] vis = new boolean[n];
for (int i=0;i<n;i++) {
nodes[i] = new Node(slices[i]);
nodes[i].index = i;
pq.add(nodes[i]);
}
for (int i=0;i<n;i++) {
nodes[i].pre = nodes[(i-1+n)%n];
nodes[i].next = nodes[(i+1)%n];
}
while (step < maxStep) {
Node cur = pq.poll();
if (!vis[cur.index]) {
step += 1;
ans += (int)cur.data;
cur.data = (int)cur.pre.data + (int)cur.next.data - (int)cur.data;
vis[cur.pre.index] = true;
vis[cur.next.index] = true;
// 这里需要注意,需要将前后节点进行删除。
cur.pre = cur.pre.pre;
cur.pre.next = cur;
cur.next = cur.next.next;
cur.next.pre = cur;
pq.add(cur); // 后悔操作
// System.out.println(step + " " + cur.index + " " + ans);
}
}
return ans;
}
}