leetcode
leetcode 1251 ~ 1300
3n 块披萨

3n 块披萨

难度:

标签:

题目描述

给你一个披萨,它由 3n 块不同大小的部分组成,现在你和你的朋友们需要按照如下规则来分披萨:

  • 你挑选 任意 一块披萨。
  • Alice 将会挑选你所选择的披萨逆时针方向的下一块披萨。
  • Bob 将会挑选你所选择的披萨顺时针方向的下一块披萨。
  • 重复上述过程直到没有披萨剩下。

每一块披萨的大小按顺时针方向由循环数组 slices 表示。

请你返回你可以获得的披萨大小总和的最大值。

 

示例 1:

输入:slices = [1,2,3,4,5,6]
输出:10
解释:选择大小为 4 的披萨,Alice 和 Bob 分别挑选大小为 3 和 5 的披萨。然后你选择大小为 6 的披萨,Alice 和 Bob 分别挑选大小为 2 和 1 的披萨。你获得的披萨总大小为 4 + 6 = 10 。

示例 2:

输入:slices = [8,9,8,6,1,1]
输出:16
解释:两轮都选大小为 8 的披萨。如果你选择大小为 9 的披萨,你的朋友们就会选择大小为 8 的披萨,这种情况下你的总和不是最大的。

 

提示:

  • 1 <= slices.length <= 500
  • slices.length % 3 == 0
  • 1 <= slices[i] <= 1000

代码结果

运行时间: 28 ms, 内存: 16.3 MB


/*
 * Problem: You are given a pizza with 3n slices of different sizes. You, Alice, and Bob
 * need to take turns picking slices according to the rules:
 * - You pick any slice.
 * - Alice picks the next slice counterclockwise.
 * - Bob picks the next slice clockwise.
 * - Repeat until all slices are taken.
 * The goal is to maximize the total size of slices you can collect.
 */
import java.util.stream.IntStream;

public class Solution {
    public int maxSizeSlices(int[] slices) {
        int n = slices.length / 3;
        return Math.max(maxSizeSlicesHelper(slices, 0, slices.length - 2, n),
                        maxSizeSlicesHelper(slices, 1, slices.length - 1, n));
    }
    
    private int maxSizeSlicesHelper(int[] slices, int start, int end, int n) {
        int[][] dp = new int[end - start + 2][n + 1];
        IntStream.range(1, end - start + 2).forEach(i ->
            IntStream.range(1, n + 1).forEach(j ->
                dp[i][j] = Math.max(dp[i - 1][j], (i >= 2 ? dp[i - 2][j - 1] : 0) + slices[start + i - 1])
            )
        );
        return dp[end - start + 1][n];
    }
}

解释

方法:

这道题的思路是使用贪心算法加上双向链表和最小堆。首先,为了方便处理环形数组,我们在数组两端各添加一个0,并建立双向链表来表示这个环。我们的目标是在每一轮中选择一个披萨片,使得当前披萨片加上相邻两个披萨片的总和最大,然后删除这三个披萨片,并更新相邻披萨片的值。为了快速找到这样的披萨片,我们使用最小堆来维护每个披萨片及其相邻披萨片的总和。每次从堆中取出最小的元素,检查其是否已经被删除(由于堆的延迟删除特性),如果没有被删除,则更新总和并进行相应的删除和更新操作。这个过程重复进行k次,其中k是披萨片总数除以3。

时间复杂度:

O(nlogn)

空间复杂度:

O(n)

代码细节讲解

🦆
为什么需要在数组两端各添加一个0,这样做有什么具体的作用或好处?
在数组两端各添加一个0主要是为了处理环形数组的边界情况。在环形结构中,数组的第一个元素和最后一个元素是相邻的,通过添加0可以简化编程逻辑,使得数组的起始和结束位置能够统一处理。这样,在使用双向链表表示环时,可以无需编写特殊的边界条件代码,从而降低错误率并简化代码实现。
🦆
双向链表在这个解法中起到了什么作用,是否可以通过其他数据结构替代?
双向链表在此解法中用于动态地删除和更新元素。双向链表允许在O(1)时间复杂度内删除任何一个节点,并快速更新其前驱和后继节点,这对于该题中需要频繁删除和调整元素位置的需求来说非常重要。尽管理论上可以使用数组或其他数据结构,但双向链表在此场景下提供了无与伦比的操作效率和简便性,尤其是在需要频繁地插入和删除操作时。
🦆
在使用最小堆维护每个披萨片及其相邻披萨片的总和时,为什么选择最小堆而不是最大堆?
题解中使用最小堆可能是一个错误,因为题目要求是在每一轮中选择一个披萨片,使得当前披萨片加上相邻两个披萨片的总和最大。因此,应该使用最大堆来维护每个披萨片及其相邻披萨片的总和,以便每次都能从堆中取出当前总和最大的元素。如果确实使用了最小堆,这可能是题解中的一个逻辑错误或者是描述错误。
🦆
堆中元素的延迟删除策略是如何实现的?能否详细解释其必要性和具体操作?
堆中的延迟删除策略是通过在堆操作中结合一个标记数组来实现的。在这种策略中,当一个元素从逻辑上被删除(即不再是堆中的有效元素)时,并不立即从物理上从堆中移除,而是简单地标记这个元素为已删除。当这个元素浮到堆顶部,堆的操作(如pop)会检查该元素是否被标记为删除,如果是,则丢弃它并继续从堆中移除下一个元素。这种策略的必要性在于它避免了复杂和耗时的堆重构操作,尤其是在连续删除多个堆元素的情况下,可以显著提高效率。

相关问题