1388. Pizza With 3n Slices
Problem Description
You have a circular pizza divided into 3n
slices, where each slice has a different size represented by an integer value. You're playing a game with two friends (Alice and Bob) to divide the pizza following these rules:
- You pick any slice of pizza to start
- Alice immediately takes the slice next to yours in the counter-clockwise direction
- Bob immediately takes the slice next to yours in the clockwise direction
- This process repeats - you pick another available slice, then Alice and Bob take the adjacent slices
- The game continues until all slices are taken
Given an array slices
where slices[i]
represents the size of the i-th slice in clockwise order around the circular pizza, you need to find the maximum total sum of slice sizes you can obtain.
The key insight is that this problem can be transformed into selecting n
non-adjacent elements from a circular array of size 3n
to maximize their sum. This is because:
- When you pick a slice, Alice and Bob take the adjacent slices, making those three slices unavailable
- You get to pick
n
times total (since3n
slices divided among 3 people givesn
slices each) - Your selections cannot be adjacent in the original circle (since picking one slice removes its neighbors)
The circular nature of the array means if you select the first element, you cannot select the last element (they're adjacent in a circle). Therefore, the solution considers two cases: either exclude the first element or exclude the last element from consideration, then solve for the maximum sum in each linear array case.
Intuition
The first observation is understanding what happens when you pick a slice. Once you choose a slice, Alice and Bob immediately take the slices on either side, effectively removing three consecutive slices from play. This means you can never pick two adjacent slices in the original circle.
Since there are 3n
total slices and three people, each person gets exactly n
slices. The challenge is to maximize your n
selections while ensuring no two of your picks are adjacent in the original circular arrangement.
This constraint - selecting n
elements from 3n
elements where no two selections are adjacent - is a classic dynamic programming pattern similar to the "House Robber" problem. However, the circular nature adds complexity: in a circle, the first and last elements are neighbors.
To handle the circular constraint, we can break it down into two simpler linear problems:
- Consider slices
[0, 1, 2, ..., 3n-2]
(excluding the last slice) - Consider slices
[1, 2, 3, ..., 3n-1]
(excluding the first slice)
By solving both cases separately, we ensure that we never pick both the first and last slices together (which would violate the adjacency rule in a circle). The maximum of these two solutions gives us the answer.
For each linear array, we use dynamic programming where f[i][j]
represents the maximum sum when considering the first i
elements and selecting exactly j
non-adjacent elements. At each position, we have two choices:
- Skip the current element:
f[i][j] = f[i-1][j]
- Take the current element (if we didn't take the previous one):
f[i][j] = f[i-2][j-1] + nums[i-1]
The maximum of these two choices gives us the optimal solution for that state.
Learn more about Greedy, Dynamic Programming and Heap (Priority Queue) patterns.
Solution Approach
The solution implements the dynamic programming approach through a helper function g(nums)
that solves the linear version of the problem: selecting n
non-adjacent elements from an array to maximize their sum.
Main Function Structure:
- Calculate
n = len(slices) // 3
to determine how many slices you get to pick - Call
g(slices[:-1])
to solve for the case excluding the last element - Call
g(slices[1:])
to solve for the case excluding the first element - Return the maximum of these two results
Helper Function g(nums)
Implementation:
The function uses a 2D DP table f
where f[i][j]
represents the maximum sum when considering the first i
elements and selecting exactly j
non-adjacent elements.
-
Initialization: Create a table
f
of size(m+1) × (n+1)
initialized with zeros, wherem = len(nums)
-
State Transition: For each position
i
from 1 tom
and each countj
from 1 ton
:f[i][j] = max( f[i-1][j], # Don't take element i (f[i-2][j-1] if i >= 2 else 0) + nums[i-1] # Take element i )
f[i-1][j]
: Maximum sum without taking the current elementf[i-2][j-1] + nums[i-1]
: Maximum sum when taking the current element (we must skip the previous element, hencei-2
, and we've selected one more element, hencej-1
)
-
Edge Case Handling: When
i < 2
, we handle it specially by using 0 forf[i-2][j-1]
since there's no element two positions back -
Result: Return
f[m][n]
, which represents the maximum sum when considering allm
elements and selecting exactlyn
non-adjacent ones
Time Complexity: O(n²)
for each call to g()
, and we call it twice, so overall O(n²)
Space Complexity: O(n²)
for the DP table
The elegance of this solution lies in transforming a complex circular selection problem into two simpler linear problems, then applying classical dynamic programming to solve each one optimally.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with slices = [3, 1, 4, 2, 5, 6]
(6 slices total, so n = 2).
Step 1: Understanding the Problem
- We have a circular pizza with 6 slices
- We need to pick 2 slices (n = 6/3 = 2)
- When we pick a slice, our friends take the adjacent ones
- We want to maximize our total
Step 2: Transform to Linear Problems Since the array is circular, slices[0] and slices[5] are adjacent. We handle this by solving two cases:
- Case 1: Consider slices
[3, 1, 4, 2, 5]
(exclude last element 6) - Case 2: Consider slices
[1, 4, 2, 5, 6]
(exclude first element 3)
Step 3: Solve Case 1 - g([3, 1, 4, 2, 5])
We need to select 2 non-adjacent elements from [3, 1, 4, 2, 5]
.
Building our DP table f[i][j]
where i = elements considered, j = elements selected:
i\j | 0 | 1 | 2 |
---|---|---|---|
0 | 0 | 0 | 0 |
1 | 0 | 3 | 0 |
2 | 0 | 3 | 0 |
3 | 0 | 4 | 7 |
4 | 0 | 4 | 7 |
5 | 0 | 5 | 9 |
Key calculations:
f[3][2] = max(f[2][2], f[1][1] + 4) = max(0, 3 + 4) = 7
(picking elements at indices 0 and 2: values 3 and 4)f[5][2] = max(f[4][2], f[3][1] + 5) = max(7, 4 + 5) = 9
(picking elements at indices 2 and 4: values 4 and 5)
Case 1 maximum: 9
Step 4: Solve Case 2 - g([1, 4, 2, 5, 6])
We need to select 2 non-adjacent elements from [1, 4, 2, 5, 6]
.
Building our DP table:
i\j | 0 | 1 | 2 |
---|---|---|---|
0 | 0 | 0 | 0 |
1 | 0 | 1 | 0 |
2 | 0 | 4 | 0 |
3 | 0 | 4 | 3 |
4 | 0 | 5 | 9 |
5 | 0 | 6 | 10 |
Key calculations:
f[4][2] = max(f[3][2], f[2][1] + 5) = max(3, 4 + 5) = 9
(picking elements at indices 1 and 3: values 4 and 5)f[5][2] = max(f[4][2], f[3][1] + 6) = max(9, 4 + 6) = 10
(picking elements at indices 1 and 4: values 4 and 6)
Case 2 maximum: 10
Step 5: Final Answer The maximum of Case 1 (9) and Case 2 (10) is 10.
This means we should pick slices with values 4 and 6 from the original circular arrangement (indices 2 and 5), which are indeed non-adjacent in the circle, giving us a total of 10.
Solution Implementation
1class Solution:
2 def maxSizeSlices(self, slices: List[int]) -> int:
3 """
4 Find the maximum sum of n non-adjacent slices from a circular array.
5 This is equivalent to choosing n/3 slices from n total slices where no two are adjacent.
6
7 Args:
8 slices: List of pizza slice sizes arranged in a circle
9
10 Returns:
11 Maximum sum of sizes of n/3 non-adjacent slices
12 """
13 def max_sum_non_adjacent(nums: List[int]) -> int:
14 """
15 Helper function to find maximum sum of selecting 'slices_to_pick' elements
16 from a linear array where no two selected elements are adjacent.
17
18 Args:
19 nums: Linear array of numbers
20
21 Returns:
22 Maximum sum of non-adjacent elements
23 """
24 array_length = len(nums)
25
26 # dp[i][j] represents max sum when considering first i elements
27 # and selecting exactly j elements
28 dp = [[0] * (slices_to_pick + 1) for _ in range(array_length + 1)]
29
30 # Fill the DP table
31 for i in range(1, array_length + 1):
32 for j in range(1, slices_to_pick + 1):
33 # Option 1: Don't take current element
34 dont_take = dp[i - 1][j]
35
36 # Option 2: Take current element (if possible)
37 # We need at least 2 elements to have a previous non-adjacent element
38 take = (dp[i - 2][j - 1] if i >= 2 else 0) + nums[i - 1]
39
40 dp[i][j] = max(dont_take, take)
41
42 return dp[array_length][slices_to_pick]
43
44 # Calculate number of slices we need to pick (n/3 of total)
45 slices_to_pick = len(slices) // 3
46
47 # Handle circular array by considering two cases:
48 # Case 1: Exclude last element (so first and last aren't both selected)
49 max_without_last = max_sum_non_adjacent(slices[:-1])
50
51 # Case 2: Exclude first element (so first and last aren't both selected)
52 max_without_first = max_sum_non_adjacent(slices[1:])
53
54 # Return the maximum of both cases
55 return max(max_without_last, max_without_first)
56
1class Solution {
2 private int maxSlicesToPick;
3
4 /**
5 * Finds the maximum sum of pizza slices you can pick.
6 * This is similar to House Robber II problem - you cannot pick adjacent slices,
7 * and the array is circular (first and last elements are adjacent).
8 *
9 * @param slices Array of pizza slice values
10 * @return Maximum sum of n/3 non-adjacent slices
11 */
12 public int maxSizeSlices(int[] slices) {
13 // Calculate how many slices we need to pick (n/3 slices from n total)
14 maxSlicesToPick = slices.length / 3;
15
16 // Create array excluding the first element (to break circular dependency)
17 int[] excludeFirst = new int[slices.length - 1];
18 System.arraycopy(slices, 1, excludeFirst, 0, excludeFirst.length);
19 int maxWithoutFirst = calculateMaxSum(excludeFirst);
20
21 // Create array excluding the last element (to break circular dependency)
22 int[] excludeLast = new int[slices.length - 1];
23 System.arraycopy(slices, 0, excludeLast, 0, excludeLast.length);
24 int maxWithoutLast = calculateMaxSum(excludeLast);
25
26 // Return the maximum of both scenarios
27 return Math.max(maxWithoutFirst, maxWithoutLast);
28 }
29
30 /**
31 * Calculates maximum sum by picking exactly 'maxSlicesToPick' non-adjacent elements.
32 * Uses dynamic programming approach.
33 *
34 * @param nums Array of values to pick from
35 * @return Maximum sum of non-adjacent elements
36 */
37 private int calculateMaxSum(int[] nums) {
38 int arrayLength = nums.length;
39
40 // dp[i][j] represents max sum using first i elements and picking exactly j elements
41 int[][] dp = new int[arrayLength + 1][maxSlicesToPick + 1];
42
43 // Fill the DP table
44 for (int i = 1; i <= arrayLength; ++i) {
45 for (int j = 1; j <= maxSlicesToPick; ++j) {
46 // Option 1: Don't pick current element
47 int skipCurrent = dp[i - 1][j];
48
49 // Option 2: Pick current element (must skip previous element)
50 int pickCurrent = 0;
51 if (i >= 2) {
52 // Can pick current element if we have at least 2 elements
53 pickCurrent = dp[i - 2][j - 1] + nums[i - 1];
54 } else if (i == 1 && j == 1) {
55 // Special case: first element when we need to pick exactly 1
56 pickCurrent = nums[0];
57 }
58
59 // Take maximum of both options
60 dp[i][j] = Math.max(skipCurrent, pickCurrent);
61 }
62 }
63
64 // Return maximum sum using all elements and picking exactly maxSlicesToPick elements
65 return dp[arrayLength][maxSlicesToPick];
66 }
67}
68
1class Solution {
2public:
3 int maxSizeSlices(vector<int>& slices) {
4 int totalSlices = slices.size();
5 int numToSelect = totalSlices / 3;
6
7 // Lambda function to find max sum of k non-adjacent elements in a linear array
8 auto maxSumNonAdjacent = [&](vector<int>& nums, int k) -> int {
9 int arraySize = nums.size();
10
11 // dp[i][j] = maximum sum selecting j elements from first i elements
12 vector<vector<int>> dp(arraySize + 1, vector<int>(k + 1, 0));
13
14 // Fill the DP table
15 for (int i = 1; i <= arraySize; ++i) {
16 for (int j = 1; j <= k; ++j) {
17 // Option 1: Don't take current element
18 int skipCurrent = dp[i - 1][j];
19
20 // Option 2: Take current element (must skip previous element)
21 int takeCurrent = 0;
22 if (i >= 2) {
23 takeCurrent = dp[i - 2][j - 1] + nums[i - 1];
24 } else if (i == 1 && j == 1) {
25 takeCurrent = nums[0];
26 }
27
28 dp[i][j] = max(skipCurrent, takeCurrent);
29 }
30 }
31
32 return dp[arraySize][k];
33 };
34
35 // Handle circular array by considering two cases:
36 // Case 1: Exclude last element (can potentially include first)
37 vector<int> excludeLast(slices.begin(), slices.end() - 1);
38 int maxWithoutLast = maxSumNonAdjacent(excludeLast, numToSelect);
39
40 // Case 2: Exclude first element (can potentially include last)
41 vector<int> excludeFirst(slices.begin() + 1, slices.end());
42 int maxWithoutFirst = maxSumNonAdjacent(excludeFirst, numToSelect);
43
44 // Return the maximum of both cases
45 return max(maxWithoutLast, maxWithoutFirst);
46 }
47};
48
1/**
2 * Solves the "Pizza With 3n Slices" problem
3 * Given a circular array of pizza slices, select n non-adjacent slices to maximize their sum
4 * where n = array.length / 3
5 *
6 * @param slices - Array of pizza slice values arranged in a circle
7 * @returns Maximum sum of n non-adjacent slices
8 */
9function maxSizeSlices(slices: number[]): number {
10 // Calculate how many slices we need to pick (1/3 of total slices)
11 const slicesToPick: number = Math.floor(slices.length / 3);
12
13 /**
14 * Helper function to find maximum sum of k non-adjacent elements from a linear array
15 * Uses dynamic programming approach
16 *
17 * @param nums - Linear array of numbers
18 * @returns Maximum sum of slicesToPick non-adjacent elements
19 */
20 const findMaxNonAdjacentSum = (nums: number[]): number => {
21 const arrayLength: number = nums.length;
22
23 // dp[i][j] represents max sum when considering first i elements and picking j elements
24 const dp: number[][] = Array(arrayLength + 1)
25 .fill(0)
26 .map(() => Array(slicesToPick + 1).fill(0));
27
28 // Fill the DP table
29 for (let i = 1; i <= arrayLength; ++i) {
30 for (let j = 1; j <= slicesToPick; ++j) {
31 // Option 1: Don't pick current element, take result from previous element
32 const skipCurrent: number = dp[i - 1][j];
33
34 // Option 2: Pick current element (if possible)
35 // Must skip the previous element, so use dp[i-2][j-1]
36 const pickCurrent: number = (i > 1 ? dp[i - 2][j - 1] : 0) + nums[i - 1];
37
38 // Take the maximum of both options
39 dp[i][j] = Math.max(skipCurrent, pickCurrent);
40 }
41 }
42
43 return dp[arrayLength][slicesToPick];
44 };
45
46 // Since the array is circular, first and last elements cannot both be selected
47 // Case 1: Consider all elements except the last one (index 0 to n-2)
48 const excludeLastSlice: number = findMaxNonAdjacentSum(slices.slice(0, -1));
49
50 // Case 2: Consider all elements except the first one (index 1 to n-1)
51 const excludeFirstSlice: number = findMaxNonAdjacentSum(slices.slice(1));
52
53 // Return the maximum of both cases
54 return Math.max(excludeLastSlice, excludeFirstSlice);
55}
56
Time and Space Complexity
The time complexity is O(n²)
, where n
is the length of the array slices
.
The function g
is called twice, each processing an array of length n-1
. Inside function g
:
- There are two nested loops: the outer loop runs
m
times (wherem = n-1
), and the inner loop runsn/3
times - Since
n/3
is proportional ton
, the nested loops result inO(m × n/3) = O(n × n) = O(n²)
operations - Each operation inside the loops takes constant time
O(1)
The space complexity is O(n²)
.
The 2D array f
has dimensions (m+1) × (n+1)
, where m = n-1
and n
in the context of function g
is len(slices) // 3
. This creates an array of size approximately n × (n/3) = O(n²)
. The function g
is called twice, but the space is not accumulated as each call creates its own local array, so the overall space complexity remains O(n²)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Handling of the Circular Constraint
Pitfall: A common mistake is trying to solve the circular array problem directly without breaking it into two linear cases. Developers often attempt to modify the DP transition to handle the circular constraint within a single pass, which leads to incorrect results.
Why it happens: The circular constraint (first and last elements cannot both be selected) creates a dependency that's difficult to handle in a single DP formulation.
Solution: Always break the circular array into two linear arrays:
- Case 1: Consider elements
[0, n-2]
(exclude last) - Case 2: Consider elements
[1, n-1]
(exclude first)
Then solve both cases independently and take the maximum.
2. Off-by-One Errors in DP Table Indexing
Pitfall: Mixing up 0-based array indexing with 1-based DP table indexing, leading to accessing wrong elements or array out of bounds errors.
# WRONG: Forgetting to subtract 1 when accessing array take = dp[i-2][j-1] + nums[i] # nums[i] would be out of bounds! # CORRECT: Proper index adjustment take = dp[i-2][j-1] + nums[i-1] # i is 1-based, nums is 0-based
Solution: Be consistent with indexing. If using 1-based indexing for DP table (where dp[i]
represents considering first i
elements), always use nums[i-1]
to access the actual array element.
3. Forgetting Edge Cases in DP Transitions
Pitfall: Not handling the case when i < 2
in the DP transition, causing index errors when trying to access dp[i-2][j-1]
.
# WRONG: No boundary check take = dp[i-2][j-1] + nums[i-1] # Fails when i = 1 # CORRECT: Proper boundary handling take = (dp[i-2][j-1] if i >= 2 else 0) + nums[i-1]
Solution: Always check boundaries when accessing previous states in DP. When i < 2
, there's no valid "two positions back" state, so use 0 as the base case.
4. Incorrect Calculation of Number of Selections
Pitfall: Misunderstanding how many slices you get to pick. Some might think you pick n
slices from n
total, but the problem states you have 3n
slices and pick n
of them.
# WRONG: Using wrong number of selections
slices_to_pick = len(slices) # This would be 3n, not n!
# CORRECT: Proper calculation
slices_to_pick = len(slices) // 3 # This gives n
Solution: Carefully read the problem statement. You have 3n
total slices divided among 3 people, so each person gets n
slices.
5. Space Optimization Confusion
Pitfall: Attempting to optimize space from O(n²) to O(n) without fully understanding the dependencies, leading to incorrect results.
Why it's tricky: While the transition only depends on the previous two rows (dp[i-1]
and dp[i-2]
), you need to track different values for different j
values, making the optimization non-trivial.
Solution: If space optimization is needed, maintain two arrays representing the previous two rows and carefully update them. However, for interview settings, the O(n²) solution is usually acceptable and less error-prone.
What's the output of running the following function using the following tree as input?
1def serialize(root):
2 res = []
3 def dfs(root):
4 if not root:
5 res.append('x')
6 return
7 res.append(root.val)
8 dfs(root.left)
9 dfs(root.right)
10 dfs(root)
11 return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4 StringJoiner res = new StringJoiner(" ");
5 serializeDFS(root, res);
6 return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10 if (root == null) {
11 result.add("x");
12 return;
13 }
14 result.add(Integer.toString(root.val));
15 serializeDFS(root.left, result);
16 serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2 let res = [];
3 serialize_dfs(root, res);
4 return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8 if (!root) {
9 res.push("x");
10 return;
11 }
12 res.push(root.val);
13 serialize_dfs(root.left, res);
14 serialize_dfs(root.right, res);
15}
16
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
Want a Structured Path to Master System Design Too? Don’t Miss This!