Pizza With 3n Slices
HardPrompt
There is a pizza with 3n slices of varying size, you and your friends will take slices of pizza as follows:
- You will pick any pizza slice.
- Your friend Alice will pick the next slice in the anti-clockwise direction of your pick.
- Your friend Bob will pick the next slice in the clockwise direction of your pick.
- Repeat until there are no more slices of pizzas.
Given an integer array slices that represent the sizes of the pizza slices in a clockwise direction, return the maximum possible sum of slice sizes that you can pick.
Example 1:
Input: slices = [1,2,3,4,5,6]
Output: 10
Explanation: Pick pizza slice of size 4, Alice and Bob will pick slices with size 3 and 5 respectively. Then Pick slices with size 6, finally Alice and Bob will pick slice of size 2 and 1 respectively. Total = 4 + 6.Example 2:
Input: slices = [8,9,8,6,1,1]
Output: 16
Explanation: Pick pizza slice of size 8 in each turn. If you pick slice with size 9 your partners will pick slices of size 8.
Constraints:
3 * n == slices.length1 <= slices.length <= 5001 <= slices[i] <= 1000
Approaches
2 approaches with complexity analysis and trade-offs.
The problem can be rephrased as "select n non-adjacent slices from a circular array of 3n slices to maximize the sum". The circular nature means the first and last slices are adjacent and cannot be picked together. This constraint can be handled by breaking the problem into two independent subproblems for a linear array:
- Maximum sum from slices
0to3n-2. - Maximum sum from slices
1to3n-1. The final answer is the maximum of the solutions to these two subproblems. Each subproblem, which is to find the maximum sum ofnnon-adjacent items in a linear array, can be solved using dynamic programming.
Algorithm
- Let
mbe the total number of slices andn = m / 3. - To handle the circular dependency, we solve two separate problems on linear arrays:
- Find the max sum for
slices[0...m-2]. - Find the max sum for
slices[1...m-1].
- Find the max sum for
- The overall answer is the maximum of the results of these two subproblems.
- For each subproblem, we use a helper function
solve(arr, k)which calculates the maximum sum ofknon-adjacent elements from a linear arrayarr. - Inside
solve, we use a 2D DP arraydp[i][j]representing the max sum from the firstielements by pickingjof them. - The DP table is filled using the recurrence:
dp[i][j] = max(dp[i-1][j], arr[i-1] + dp[i-2][j-1]). - The result for a subproblem is
dp[arr.length][k].
Walkthrough
Let's define a function solve(arr) that finds the maximum sum of n non-adjacent elements in a linear array arr.
We use a 2D DP table, dp[i][j], to store the maximum sum we can obtain by choosing j slices from the first i slices of the input array arr.
The state transition is as follows:
For each slice arr[i-1] (using 1-based indexing for i), we have two choices:
- Don't pick
arr[i-1]: The maximum sum is the same as what we could get from the firsti-1slices by pickingjslices. This value isdp[i-1][j]. - Pick
arr[i-1]: If we pickarr[i-1], we cannot pick its neighborarr[i-2]. So, we must have pickedj-1slices from the firsti-2slices. The sum would bearr[i-1] + dp[i-2][j-1]. The recurrence relation is:dp[i][j] = max(dp[i-1][j], arr[i-1] + dp[i-2][j-1]). The base cases aredp[0][j] = 0anddp[i][0] = 0. The final result for a subproblem of lengthLisdp[L][n].
public class Solution { public int maxSizeSlices(int[] slices) { int m = slices.length; int n = m / 3; // Case 1: Exclude the last slice int[] slices1 = new int[m - 1]; System.arraycopy(slices, 0, slices1, 0, m - 1); int ans1 = solve(slices1, n); // Case 2: Exclude the first slice int[] slices2 = new int[m - 1]; System.arraycopy(slices, 1, slices2, 0, m - 1); int ans2 = solve(slices2, n); return Math.max(ans1, ans2); } private int solve(int[] arr, int k) { int len = arr.length; int[][] dp = new int[len + 1][k + 1]; for (int i = 1; i <= len; i++) { for (int j = 1; j <= k; j++) { int notPick = dp[i - 1][j]; int pick = (i >= 2 ? dp[i - 2][j - 1] : 0) + arr[i - 1]; // It's impossible to pick j items from less than 2j-1 items. // This is implicitly handled if we consider that dp[i-2][j-1] would be 0 // if it was impossible to form that sum. // A more robust way is to check explicitly. if (2 * j > i + 1) { // or 2*j-1 > i dp[i][j] = dp[i-1][j]; } else { dp[i][j] = Math.max(pick, notPick); } } } return dp[len][k]; }}Complexity
Time
O(m * n) or O(m^2) since `n = m/3`. We run the `solve` function twice, and each call involves nested loops of size `m-1` and `n`.
Space
O(m * n) or O(m^2). The space is dominated by the 2D DP table of size `(m-1+1) x (n+1)`.
Trade-offs
Pros
It's a standard and clear dynamic programming solution that correctly solves the problem.
The logic is a direct translation of the recurrence relation.
Cons
The space complexity is
O(m*n), which can be large for the given constraints, although it fits within typical memory limits.
This approach builds upon the previous DP solution by optimizing its space complexity. In the recurrence dp[i][j] = max(dp[i-1][j], arr[i-1] + dp[i-2][j-1]), we can observe that the computation for the current row i only depends on the two preceding rows, i-1 and i-2. This allows us to avoid storing the entire 2D DP table and instead only keep track of the last two rows.
Algorithm
- The overall structure is identical to the first approach: solve two linear subproblems for
slices[0...m-2]andslices[1...m-1]. - The
solve(arr, k)function is optimized for space. - Initialize two 1D arrays,
prev2andprev1, of sizek+1to represent the DP states for rowsi-2andi-1. - Iterate
ifrom 1 toarr.length. In each iteration:- Create a new 1D array
currof sizek+1for the current rowi. - Iterate
jfrom 1 tokand computecurr[j] = max(prev1[j], arr[i-1] + prev2[j-1]), with a check for feasibility. - After the inner loop, update
prev2 = prev1andprev1 = curr.
- Create a new 1D array
- The result for the subproblem is
prev1[k].
Walkthrough
The core idea remains the same: break the circular array problem into two linear array subproblems. The optimization lies within the solve helper function.
Instead of a (len+1) x (k+1) DP table, we use three 1D arrays, say prev2, prev1, and curr, each of size k+1.
prev2stores the DP values for rowi-2.prev1stores the DP values for rowi-1.curris used to compute the DP values for the current rowi.
The update rule becomes: curr[j] = max(prev1[j], arr[i-1] + prev2[j-1]).
After iterating through all j for a given i, we update the pointers for the next iteration: prev2 becomes prev1, and prev1 becomes curr. This rolling array mechanism reduces the space from O(m*n) to O(n).
public class Solution { public int maxSizeSlices(int[] slices) { int m = slices.length; int n = m / 3; int[] slices1 = new int[m - 1]; System.arraycopy(slices, 0, slices1, 0, m - 1); int ans1 = solve(slices1, n); int[] slices2 = new int[m - 1]; System.arraycopy(slices, 1, slices2, 0, m - 1); int ans2 = solve(slices2, n); return Math.max(ans1, ans2); } private int solve(int[] arr, int k) { int len = arr.length; if (k == 0) { return 0; } int[] prev2 = new int[k + 1]; // Corresponds to dp[i-2] int[] prev1 = new int[k + 1]; // Corresponds to dp[i-1] for (int i = 1; i <= len; i++) { int[] curr = new int[k + 1]; // Corresponds to dp[i] for (int j = 1; j <= k; j++) { if (2 * j - 1 > i) { curr[j] = prev1[j]; } else { int notPick = prev1[j]; int pick = arr[i - 1] + prev2[j - 1]; curr[j] = Math.max(pick, notPick); } } prev2 = prev1; prev1 = curr; } return prev1[k]; }}Complexity
Time
O(m * n) or O(m^2). The number of computations is the same as the unoptimized version.
Space
O(n) or O(m). We only need to store a few arrays of size `n+1` to compute the result.
Trade-offs
Pros
Highly efficient in terms of space, reducing it from quadratic to linear.
Maintains the same time efficiency as the unoptimized DP approach.
Cons
The implementation can be slightly trickier to get right due to managing the rolling arrays.
Solutions
Solution
class Solution {private int n;public int maxSizeSlices(int[] slices) { n = slices.length / 3; int[] nums = new int[slices.length - 1]; System.arraycopy(slices, 1, nums, 0, nums.length); int a = g(nums); System.arraycopy(slices, 0, nums, 0, nums.length); int b = g(nums); return Math.max(a, b); }private int g(int[] nums) { int m = nums.length; int[][] f = new int[m + 1][n + 1]; for (int i = 1; i <= m; ++i) { for (int j = 1; j <= n; ++j) { f[i][j] = Math.max(f[i - 1][j], (i >= 2 ? f[i - 2][j - 1] : 0) + nums[i - 1]); } } return f[m][n]; }}class Solution {public: int maxSizeSlices(vector<int> &slices) { int n = slices.size() / 3; auto g = [&](vector<int> &nums) -> int { int m = nums.size(); int f[m + 1][n + 1]; memset(f, 0, sizeof f); for (int i = 1; i <= m; ++i) { for (int j = 1; j <= n; ++j) { f[i][j] = max(f[i - 1][j], (i >= 2 ? f[i - 2][j - 1] : 0) + nums[i - 1]); } } return f[m][n]; }; vector<int> nums(slices.begin(), slices.end() - 1); int a = g(nums); nums = vector<int>(slices.begin() + 1, slices.end()); int b = g(nums); return max(a, b); }};class Solution: def maxSizeSlices(self, slices: List[int]) -> int: def g(nums: List[int]) -> int: m = len(nums) f = [[0] * (n + 1) for _ in range(m + 1)] for i in range(1, m + 1): for j in range(1, n + 1): f[i][j] = max(f[i - 1][j], (f[i - 2][j - 1] if i >= 2 else 0) + nums[i - 1]) return f[m][n] n = len(slices) // 3 a, b = g(slices[: - 1]), g(slices[1:]) return max(a, b)Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.