每日一题:leetcode1338 3n块披萨

文章讨论了一种关于如何在分配披萨时最大化个人获得的总披萨大小的问题,通过贪心策略和优先队列模拟,找到在给定限制下选择不连续部分的最大和。
摘要由CSDN通过智能技术生成

给你一个披萨,它由 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):


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;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值