每日一题:leetcode1338 3n块披萨

给你一个披萨,它由 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;
    }
}
相关推荐
passer__jw7671 小时前
【LeetCode】【算法】3. 无重复字符的最长子串
算法·leetcode
passer__jw7671 小时前
【LeetCode】【算法】21. 合并两个有序链表
算法·leetcode·链表
sweetheart7-71 小时前
LeetCode22. 括号生成(2024冬季每日一题 2)
算法·深度优先·力扣·dfs·左右括号匹配
景鹤4 小时前
【算法】递归+回溯+剪枝:78.子集
算法·机器学习·剪枝
_OLi_4 小时前
力扣 LeetCode 704. 二分查找(Day1:数组)
算法·leetcode·职场和发展
丶Darling.4 小时前
Day40 | 动态规划 :完全背包应用 组合总和IV(类比爬楼梯)
c++·算法·动态规划·记忆化搜索·回溯
风影小子5 小时前
IO作业5
算法
奶味少女酱~5 小时前
常用的c++特性-->day02
开发语言·c++·算法
passer__jw7675 小时前
【LeetCode】【算法】11. 盛最多水的容器
算法·leetcode
诸葛悠闲5 小时前
C++ 面试问题集合
算法