2735. Collecting Chocolates
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 typei
is at positioni
) - 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 typei
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).
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, typei
is only available at positioni
- For each number of operations
j
(from 1 to n-1):- Calculate the position where type
i
appears afterj
operations:(i - j) % n
- Update
f[i][j] = min(f[i][j-1], nums[(i - j) % n])
- Calculate the position where type
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 performingj
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 EvaluatorExample 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 = 290j = 1
: Collection cost = 15 + 15 + 56 + 56 = 142, Operation cost = 6, Total = 148j = 2
: Collection cost = 15 + 15 + 15 + 56 = 101, Operation cost = 12, Total = 113j = 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 dimensionsn × n
- The nested loops iterate through each element
(i, j)
wherei
ranges from0
ton-1
andj
ranges from0
ton-1
- For each position
f[i][j]
, the computation involves:- When
j = 0
: Direct assignment withO(1)
operation - When
j > 0
: Amin
operation comparing two values withO(1)
operation
- When
- Total iterations:
n × n = n²
- The final return statement computes the minimum across
n
sums, where each sum iterates throughn
elements, resulting inO(n²)
operations - Overall time complexity:
O(n²) + O(n²) = O(n²)
Space Complexity Analysis:
- The 2D array
f
has dimensionsn × n
, requiringO(n²)
space - Other variables (
i
,j
,v
) use constant spaceO(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)
)
Which of the following is a good use case for backtracking?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!