Facebook Pixel

2735. Collecting Chocolates

MediumArrayEnumeration
Leetcode Link

Problem Description

You have a 0-indexed array nums of size n where each element represents the cost of collecting a chocolate. Initially, the chocolate at index i is of type i.

You can perform an operation that costs x units, which simultaneously shifts all chocolate types forward by one position (cyclically). Specifically, chocolate of type i becomes type (i + 1) mod n for all chocolates.

Your goal is to collect one chocolate of each type (types 0 through n-1) with minimum total cost.

For example, if you have chocolates at positions [0, 1, 2] with types [0, 1, 2] initially:

  • After 0 operations: types remain [0, 1, 2]
  • After 1 operation (cost x): types become [1, 2, 0]
  • After 2 operations (cost 2x): types become [2, 0, 1]

The key insight is that after j operations, the chocolate at position i will be of type (i + j) mod n. This means type t can be found at position (t - j + n) mod n after j operations.

The solution uses dynamic programming where f[i][j] represents the minimum cost to get a chocolate of type i after performing j operations. For each type i:

  • With 0 operations: f[i][0] = nums[i] (chocolate of type i is at position i)
  • With j operations: f[i][j] = min(f[i][j-1], nums[(i - j) % n]) (we can either use a previously found cheaper option or the chocolate now at the position where type i appears)

The final answer is the minimum over all possible numbers of operations j (from 0 to n-1) of: sum(f[i][j] for all types i) + x * j (total collection cost plus operation cost).

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

Intuition

The key observation is that we need to collect exactly one chocolate of each type, and we can perform operations to shift the types around. Since each operation shifts ALL types simultaneously, we need to think about which configuration (after how many operations) gives us the minimum total cost.

Let's think about what happens after j operations. Each chocolate's type shifts by j positions cyclically. So if we want to find where type i is located after j operations, we need to work backwards: type i is now at position (i - j + n) % n.

This means for any given number of operations j, we have a fixed mapping of which chocolates we can choose from for each type. The question becomes: for each possible value of j (from 0 to n-1), what's the cheapest way to collect all types?

Here's where the dynamic programming insight comes in. For each type i, we want to track the minimum cost to obtain it after j operations. As j increases from 0 to n-1, we're essentially expanding our "window of choice" for each type.

When j = 0, we have no choice - type i is only at position i, so f[i][0] = nums[i].

When j = 1, type i can be found at its original position or at position (i - 1 + n) % n. We take the minimum: f[i][1] = min(f[i][0], nums[(i - 1 + n) % n]).

This pattern continues - as we increase j, we get one more position to potentially find a cheaper chocolate of type i. The beauty is that f[i][j] can be computed incrementally using f[i][j-1] and the new position that becomes available.

After n-1 operations, we've cycled through all possible positions for each type, so there's no benefit to considering j >= n (we'd just be repeating configurations with higher operation costs).

Finally, for each possible number of operations j, we calculate the total cost as sum(f[i][j] for all i) + x * j and take the minimum across all values of j.

Solution Approach

We implement a dynamic programming solution with a 2D array f where f[i][j] represents the minimum cost to collect a chocolate of type i after performing j operations.

Step 1: Initialize the DP table

We create a 2D array f of size n × n:

f = [[0] * n for _ in range(n)]

Step 2: Build the DP table

For each type i (from 0 to n-1):

  • Initialize f[i][0] = nums[i] - with 0 operations, type i is only available at position i
  • For each number of operations j (from 1 to n-1):
    • Calculate the position where type i appears after j operations: (i - j) % n
    • Update f[i][j] = min(f[i][j-1], nums[(i - j) % n])

This gives us the recurrence relation:

f[i][j] = nums[i]                                    if j = 0
f[i][j] = min(f[i][j-1], nums[(i - j) % n])         if 0 < j ≤ n-1

The key insight is that f[i][j-1] already contains the minimum cost among all positions checked for type i up to j-1 operations. We only need to compare it with the new position that becomes available at j operations.

Step 3: Calculate the minimum total cost

After building the DP table, we enumerate all possible numbers of operations j (from 0 to n-1) and calculate the total cost for each:

  • Collection cost: sum(f[i][j] for i in range(n)) - sum of minimum costs for all types
  • Operation cost: x * j - cost of performing j operations
  • Total cost: sum(f[i][j] for i in range(n)) + x * j

The answer is the minimum among all these total costs:

return min(sum(f[i][j] for i in range(n)) + x * j for j in range(n))

Time Complexity: O(n²) - We fill an n × n DP table, and for each j we sum over n elements.

Space Complexity: O(n²) - We use a 2D array of size n × n to store the DP values.

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 concrete example to illustrate the solution approach.

Given:

  • nums = [15, 150, 56, 69] (costs at positions 0, 1, 2, 3)
  • x = 6 (cost per operation)
  • n = 4 (number of chocolates/types)

Initial Setup:

  • Position: [0, 1, 2, 3]
  • Type: [0, 1, 2, 3] (initially, position i has type i)
  • Cost: [15, 150, 56, 69]

Step 1: Build the DP table

We'll build f[i][j] where i is the type (0-3) and j is the number of operations (0-3).

For j = 0 (no operations):

  • f[0][0] = nums[0] = 15 (type 0 at position 0)
  • f[1][0] = nums[1] = 150 (type 1 at position 1)
  • f[2][0] = nums[2] = 56 (type 2 at position 2)
  • f[3][0] = nums[3] = 69 (type 3 at position 3)

For j = 1 (after 1 operation, types shift: [1, 2, 3, 0]):

  • Type 0 is now at position 3: f[0][1] = min(f[0][0], nums[(0-1)%4]) = min(15, nums[3]) = min(15, 69) = 15
  • Type 1 is now at position 0: f[1][1] = min(f[1][0], nums[(1-1)%4]) = min(150, nums[0]) = min(150, 15) = 15
  • Type 2 is now at position 1: f[2][1] = min(f[2][0], nums[(2-1)%4]) = min(56, nums[1]) = min(56, 150) = 56
  • Type 3 is now at position 2: f[3][1] = min(f[3][0], nums[(3-1)%4]) = min(69, nums[2]) = min(69, 56) = 56

For j = 2 (after 2 operations, types shift: [2, 3, 0, 1]):

  • Type 0 is now at position 2: f[0][2] = min(f[0][1], nums[(0-2)%4]) = min(15, nums[2]) = min(15, 56) = 15
  • Type 1 is now at position 3: f[1][2] = min(f[1][1], nums[(1-2)%4]) = min(15, nums[3]) = min(15, 69) = 15
  • Type 2 is now at position 0: f[2][2] = min(f[2][1], nums[(2-2)%4]) = min(56, nums[0]) = min(56, 15) = 15
  • Type 3 is now at position 1: f[3][2] = min(f[3][1], nums[(3-2)%4]) = min(56, nums[1]) = min(56, 150) = 56

For j = 3 (after 3 operations, types shift: [3, 0, 1, 2]):

  • Type 0 is now at position 1: f[0][3] = min(f[0][2], nums[(0-3)%4]) = min(15, nums[1]) = min(15, 150) = 15
  • Type 1 is now at position 2: f[1][3] = min(f[1][2], nums[(1-3)%4]) = min(15, nums[2]) = min(15, 56) = 15
  • Type 2 is now at position 3: f[2][3] = min(f[2][2], nums[(2-3)%4]) = min(15, nums[3]) = min(15, 69) = 15
  • Type 3 is now at position 0: f[3][3] = min(f[3][2], nums[(3-3)%4]) = min(56, nums[0]) = min(56, 15) = 15

Complete DP Table:

       j=0   j=1   j=2   j=3
i=0:   15    15    15    15
i=1:   150   15    15    15
i=2:   56    56    15    15
i=3:   69    56    56    15

Step 2: Calculate total costs for each j

  • j = 0: Collection cost = 15 + 150 + 56 + 69 = 290, Operation cost = 0, Total = 290
  • j = 1: Collection cost = 15 + 15 + 56 + 56 = 142, Operation cost = 6, Total = 148
  • j = 2: Collection cost = 15 + 15 + 15 + 56 = 101, Operation cost = 12, Total = 113
  • j = 3: Collection cost = 15 + 15 + 15 + 15 = 60, Operation cost = 18, Total = 78

Answer: The minimum total cost is 78 (achieved with 3 operations).

This example shows how the DP table tracks the minimum cost to get each type after different numbers of operations, and how we find the optimal balance between collection costs and operation costs.

Solution Implementation

1class Solution:
2    def minCost(self, nums: List[int], x: int) -> int:
3        n = len(nums)
4      
5        # Create a 2D array to store minimum costs
6        # min_cost[i][j] = minimum cost to obtain item i after j rotations
7        min_cost = [[0] * n for _ in range(n)]
8      
9        # Initialize the minimum costs for each item
10        for item_index, initial_cost in enumerate(nums):
11            # Base case: no rotation, cost is the original price
12            min_cost[item_index][0] = initial_cost
13          
14            # Calculate minimum cost for item after 1 to n-1 rotations
15            for num_rotations in range(1, n):
16                # After rotation, we can choose the minimum between:
17                # - Previous minimum cost (from fewer rotations)
18                # - Cost at the new accessible position after rotation
19                rotated_position = (item_index - num_rotations) % n
20                min_cost[item_index][num_rotations] = min(
21                    min_cost[item_index][num_rotations - 1], 
22                    nums[rotated_position]
23                )
24      
25        # Find the minimum total cost across all possible rotation counts
26        # Total cost = sum of minimum costs for all items + rotation cost
27        min_total_cost = min(
28            sum(min_cost[item_index][num_rotations] for item_index in range(n)) + x * num_rotations 
29            for num_rotations in range(n)
30        )
31      
32        return min_total_cost
33
1class Solution {
2    public long minCost(int[] nums, int x) {
3        int n = nums.length;
4      
5        // dp[i][rotations] represents the minimum cost to obtain element at index i
6        // after performing 'rotations' number of rotations
7        int[][] minCostAfterRotations = new int[n][n];
8      
9        // Initialize the DP table
10        for (int i = 0; i < n; ++i) {
11            // Base case: no rotations, cost is the original element
12            minCostAfterRotations[i][0] = nums[i];
13          
14            // For each number of rotations from 1 to n-1
15            for (int rotations = 1; rotations < n; ++rotations) {
16                // Take minimum between:
17                // 1. Previous minimum cost (with fewer rotations)
18                // 2. Cost of element at position that would rotate to index i
19                int originalIndex = (i - rotations + n) % n;
20                minCostAfterRotations[i][rotations] = Math.min(
21                    minCostAfterRotations[i][rotations - 1], 
22                    nums[originalIndex]
23                );
24            }
25        }
26      
27        // Try all possible numbers of rotations and find the minimum total cost
28        long minTotalCost = 1L << 60;  // Initialize to a very large value
29      
30        for (int rotations = 0; rotations < n; ++rotations) {
31            // Calculate total cost for this number of rotations
32            long totalCost = (long) x * rotations;  // Cost of performing rotations
33          
34            // Add the minimum cost to obtain each element
35            for (int i = 0; i < n; ++i) {
36                totalCost += minCostAfterRotations[i][rotations];
37            }
38          
39            // Update minimum total cost
40            minTotalCost = Math.min(minTotalCost, totalCost);
41        }
42      
43        return minTotalCost;
44    }
45}
46
1class Solution {
2public:
3    long long minCost(vector<int>& nums, int x) {
4        int n = nums.size();
5      
6        // dp[i][j] represents the minimum cost to collect chocolate type i
7        // after performing j rotations
8        int dp[n][n];
9      
10        // Initialize dp table
11        for (int i = 0; i < n; ++i) {
12            // Base case: no rotation, cost is the original price
13            dp[i][0] = nums[i];
14          
15            // Calculate minimum cost for chocolate type i after j rotations
16            for (int j = 1; j < n; ++j) {
17                // Compare with previous minimum and the price at position (i-j)
18                // after j rotations (circular array)
19                dp[i][j] = min(dp[i][j - 1], nums[(i - j + n) % n]);
20            }
21        }
22      
23        // Initialize answer with a large value
24        long long minTotalCost = 1LL << 60;
25      
26        // Try all possible number of rotations (0 to n-1)
27        for (int rotations = 0; rotations < n; ++rotations) {
28            // Calculate total cost for current number of rotations
29            long long totalCost = 1LL * x * rotations;  // Cost of rotations
30          
31            // Add minimum cost for each chocolate type after 'rotations' operations
32            for (int chocolateType = 0; chocolateType < n; ++chocolateType) {
33                totalCost += dp[chocolateType][rotations];
34            }
35          
36            // Update minimum total cost
37            minTotalCost = min(minTotalCost, totalCost);
38        }
39      
40        return minTotalCost;
41    }
42};
43
1/**
2 * Calculates the minimum cost to collect all chocolates with rotation operations
3 * @param nums - Array of initial chocolate prices at each position
4 * @param x - Cost of performing one rotation operation
5 * @returns The minimum total cost to collect all chocolates
6 */
7function minCost(nums: number[], x: number): number {
8    const n: number = nums.length;
9  
10    // Create a 2D array to store minimum prices
11    // minPriceAfterRotations[i][j] represents the minimum price for chocolate i after j rotations
12    const minPriceAfterRotations: number[][] = Array.from(
13        { length: n }, 
14        () => Array(n).fill(0)
15    );
16  
17    // Calculate minimum price for each chocolate after different number of rotations
18    for (let chocolateIndex = 0; chocolateIndex < n; ++chocolateIndex) {
19        // Initial price without any rotation
20        minPriceAfterRotations[chocolateIndex][0] = nums[chocolateIndex];
21      
22        // Calculate minimum price after j rotations
23        for (let rotations = 1; rotations < n; ++rotations) {
24            // Compare with previous minimum and the price at new position after rotation
25            // (chocolateIndex - rotations + n) % n calculates the position after rotating left
26            const newPosition: number = (chocolateIndex - rotations + n) % n;
27            minPriceAfterRotations[chocolateIndex][rotations] = Math.min(
28                minPriceAfterRotations[chocolateIndex][rotations - 1], 
29                nums[newPosition]
30            );
31        }
32    }
33  
34    // Find the minimum total cost across all possible rotation counts
35    let minimumTotalCost: number = Infinity;
36  
37    for (let rotations = 0; rotations < n; ++rotations) {
38        // Calculate total cost for this number of rotations
39        let currentTotalCost: number = x * rotations; // Cost of rotation operations
40      
41        // Add the minimum price for each chocolate after this many rotations
42        for (let chocolateIndex = 0; chocolateIndex < n; ++chocolateIndex) {
43            currentTotalCost += minPriceAfterRotations[chocolateIndex][rotations];
44        }
45      
46        // Update the minimum total cost
47        minimumTotalCost = Math.min(minimumTotalCost, currentTotalCost);
48    }
49  
50    return minimumTotalCost;
51}
52

Time and Space Complexity

The time complexity is O(n²), and the space complexity is O(n²), where n is the length of the array nums.

Time Complexity Analysis:

  • The code creates a 2D array f with dimensions n × n
  • The nested loops iterate through each element (i, j) where i ranges from 0 to n-1 and j ranges from 0 to n-1
  • For each position f[i][j], the computation involves:
    • When j = 0: Direct assignment with O(1) operation
    • When j > 0: A min operation comparing two values with O(1) operation
  • Total iterations: n × n = n²
  • The final return statement computes the minimum across n sums, where each sum iterates through n elements, resulting in O(n²) operations
  • Overall time complexity: O(n²) + O(n²) = O(n²)

Space Complexity Analysis:

  • The 2D array f has dimensions n × n, requiring O(n²) space
  • Other variables (i, j, v) use constant space O(1)
  • Overall space complexity: O(n²)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Modulo Calculation for Negative Indices

One of the most common mistakes is incorrectly handling the modulo operation when calculating rotated positions. In Python, the modulo operator handles negative numbers correctly, but in some other languages or if implemented differently, this could be a source of bugs.

Pitfall Example:

# Some might write:
rotated_position = (item_index - num_rotations) % n
# In languages where modulo of negative numbers behaves differently,
# this might give unexpected results

Why it's a problem: When item_index - num_rotations is negative, different programming languages handle modulo differently. For instance, in C++ or Java, -1 % n might return -1 instead of n-1.

Solution:

# Ensure positive result by adding n before modulo
rotated_position = (item_index - num_rotations + n) % n

2. Attempting to Optimize Space Without Understanding Dependencies

Some might try to optimize space by using a 1D array instead of 2D, thinking they only need the previous state.

Pitfall Example:

# Incorrect space optimization attempt
min_cost = nums.copy()
for j in range(1, n):
    for i in range(n):
        min_cost[i] = min(min_cost[i], nums[(i - j) % n])
    # This loses information needed for different rotation counts!

Why it's a problem: We need to maintain the minimum cost for each item at each rotation count separately because the final answer requires summing costs at the same rotation count. Overwriting values loses this information.

Solution: Keep the 2D array as in the original solution, or if space optimization is critical, maintain separate arrays for current and previous states while carefully tracking results for each rotation count.

3. Off-by-One Error in Rotation Range

Pitfall Example:

# Incorrect: only considering up to n-2 rotations
for num_rotations in range(n - 1):  # Should be range(n)
    # Calculate costs...

Why it's a problem: After n rotations, the configuration returns to the original state. We need to consider all rotations from 0 to n-1 (inclusive) to find the optimal solution. Missing the last rotation could miss the optimal answer.

Solution: Always iterate through the full range of possible rotations:

for num_rotations in range(n):  # Correct: 0 to n-1 inclusive

4. Forgetting to Include Operation Cost in Final Calculation

Pitfall Example:

# Incorrect: forgetting to multiply by x
min_total_cost = min(
    sum(min_cost[i][j] for i in range(n)) + j  # Should be x * j
    for j in range(n)
)

Why it's a problem: The total cost includes both the cost of collecting chocolates AND the cost of performing operations. Each operation costs x units, so j operations cost x * j units total.

Solution: Always remember to multiply the number of operations by the operation cost:

min_total_cost = min(
    sum(min_cost[i][j] for i in range(n)) + x * j  # Correct
    for j in range(n)
)
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following is a good use case for backtracking?


Recommended Readings

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

Load More