Facebook Pixel

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:

  1. You pick any slice of pizza to start
  2. Alice immediately takes the slice next to yours in the counter-clockwise direction
  3. Bob immediately takes the slice next to yours in the clockwise direction
  4. This process repeats - you pick another available slice, then Alice and Bob take the adjacent slices
  5. 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 (since 3n slices divided among 3 people gives n 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Consider slices [0, 1, 2, ..., 3n-2] (excluding the last slice)
  2. 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.

  1. Initialization: Create a table f of size (m+1) × (n+1) initialized with zeros, where m = len(nums)

  2. State Transition: For each position i from 1 to m and each count j from 1 to n:

    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 element
    • f[i-2][j-1] + nums[i-1]: Maximum sum when taking the current element (we must skip the previous element, hence i-2, and we've selected one more element, hence j-1)
  3. Edge Case Handling: When i < 2, we handle it specially by using 0 for f[i-2][j-1] since there's no element two positions back

  4. Result: Return f[m][n], which represents the maximum sum when considering all m elements and selecting exactly n 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 Evaluator

Example 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\j012
0000
1030
2030
3047
4047
5059

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\j012
0000
1010
2040
3043
4059
50610

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 (where m = n-1), and the inner loop runs n/3 times
  • Since n/3 is proportional to n, the nested loops result in O(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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More