135. Candy
Problem Description
You have n
children standing in a line, and each child has a rating value given in an array called ratings
.
You need to distribute candies to these children following these rules:
- Every child must receive at least one candy
- If a child has a higher rating than their neighbor (the child directly to their left or right), they must receive more candies than that neighbor
Your task is to find the minimum total number of candies needed to satisfy both requirements.
For example, if you have children with ratings [1, 0, 2]
:
- The first child (rating 1) is higher than the second child (rating 0), so they need more candies than the second child
- The third child (rating 2) is higher than the second child (rating 0), so they need more candies than the second child
- One valid distribution would be
[2, 1, 2]
candies respectively, giving a total of 5 candies
The challenge is to find the minimum number of candies while ensuring that any child with a higher rating than their immediate neighbor gets more candies than that neighbor.
Intuition
The key insight is that each child's candy count is influenced by both their left and right neighbors. When we look at a child, they need to satisfy two potential constraints:
- If they have a higher rating than their left neighbor, they need more candies than the left neighbor
- If they have a higher rating than their right neighbor, they need more candies than the right neighbor
The challenge is that we can't determine the optimal candy count in a single pass because we don't know what's coming ahead. For instance, if we only traverse left to right and assign candies based on the left neighbor, we might violate the constraint with the right neighbor.
Consider the ratings [1, 2, 3, 2, 1]
. If we only go left to right:
- We'd assign
[1, 2, 3, ?, ?]
based on increasing ratings - But the child with rating 3 also needs more candies than the child with rating 2 to its right
- And that child with rating 2 needs more candies than the child with rating 1 to its right
This creates a cascading effect that we can't resolve in a single left-to-right pass.
The solution is to handle each direction separately:
- First, traverse left to right and ensure each child with a higher rating than their left neighbor gets more candies than them
- Then, traverse right to left and ensure each child with a higher rating than their right neighbor gets more candies than them
- Finally, for each child, take the maximum of these two requirements
Why take the maximum? Because a child must satisfy both constraints simultaneously. If the left constraint says they need 3 candies and the right constraint says they need 2 candies, they must have at least 3 candies to satisfy both conditions.
This two-pass approach ensures we capture all the dependencies from both directions and find the minimum candies needed while respecting all neighbor relationships.
Solution Approach
We implement the two-pass approach using two arrays to track candy requirements from each direction.
Step 1: Initialize Arrays
- Create two arrays
left
andright
, both of sizen
(number of children) - Initialize all values to 1, since each child must have at least one candy
left[i]
will store the minimum candies needed considering only left neighborsright[i]
will store the minimum candies needed considering only right neighbors
Step 2: Left-to-Right Pass
- Traverse from index 1 to
n-1
- For each position
i
, check ifratings[i] > ratings[i-1]
- If true, set
left[i] = left[i-1] + 1
- This ensures children with higher ratings than their left neighbor get more candies
Step 3: Right-to-Left Pass
- Traverse from index
n-2
down to 0 - For each position
i
, check ifratings[i] > ratings[i+1]
- If true, set
right[i] = right[i+1] + 1
- This ensures children with higher ratings than their right neighbor get more candies
Step 4: Calculate Final Result
- For each child at position
i
, their actual candy count must bemax(left[i], right[i])
- Taking the maximum ensures both left and right neighbor constraints are satisfied
- Sum up all these maximum values to get the total minimum candies needed
Example Walkthrough:
For ratings = [1, 0, 2]
:
- After left-to-right pass:
left = [1, 1, 2]
(child at index 2 needs more than child at index 1) - After right-to-left pass:
right = [2, 1, 1]
(child at index 0 needs more than child at index 1) - Final candies:
[max(1,2), max(1,1), max(2,1)] = [2, 1, 2]
- Total: 5 candies
The time complexity is O(n)
as we make three passes through the array, and the space complexity is O(n)
for the two auxiliary arrays.
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 the solution with ratings = [1, 3, 2, 2, 1]
:
Step 1: Initialize Arrays
- Create
left = [1, 1, 1, 1, 1]
andright = [1, 1, 1, 1, 1]
- Every child starts with at least 1 candy
Step 2: Left-to-Right Pass
- Index 1:
ratings[1]=3 > ratings[0]=1
, soleft[1] = left[0] + 1 = 2
- Index 2:
ratings[2]=2 < ratings[1]=3
, soleft[2]
stays 1 - Index 3:
ratings[3]=2 = ratings[2]=2
, soleft[3]
stays 1 - Index 4:
ratings[4]=1 < ratings[3]=2
, soleft[4]
stays 1 - Result:
left = [1, 2, 1, 1, 1]
Step 3: Right-to-Left Pass
- Index 3:
ratings[3]=2 > ratings[4]=1
, soright[3] = right[4] + 1 = 2
- Index 2:
ratings[2]=2 = ratings[3]=2
, soright[2]
stays 1 - Index 1:
ratings[1]=3 > ratings[2]=2
, soright[1] = right[2] + 1 = 2
- Index 0:
ratings[0]=1 < ratings[1]=3
, soright[0]
stays 1 - Result:
right = [1, 2, 1, 2, 1]
Step 4: Calculate Final Result
- Child 0:
max(left[0]=1, right[0]=1) = 1
candy - Child 1:
max(left[1]=2, right[1]=2) = 2
candies - Child 2:
max(left[2]=1, right[2]=1) = 1
candy - Child 3:
max(left[3]=1, right[3]=2) = 2
candies - Child 4:
max(left[4]=1, right[4]=1) = 1
candy
Final Distribution: [1, 2, 1, 2, 1]
Total Candies: 1 + 2 + 1 + 2 + 1 = 7 candies
Verification:
- Child 1 (rating 3) has 2 candies, more than both neighbors (1 candy each) β
- Child 3 (rating 2) has 2 candies, more than child 4 (1 candy) β
- All other neighbor relationships are satisfied β
Solution Implementation
1class Solution:
2 def candy(self, ratings: List[int]) -> int:
3 """
4 Distribute minimum candies to children based on ratings.
5 Each child must receive at least one candy.
6 Children with higher ratings than neighbors must get more candies.
7
8 Args:
9 ratings: List of integers representing children's ratings
10
11 Returns:
12 Minimum number of candies needed
13 """
14 n = len(ratings)
15
16 # Initialize arrays to track candy requirements from left and right traversals
17 left_to_right = [1] * n # Minimum candies needed considering left neighbor
18 right_to_left = [1] * n # Minimum candies needed considering right neighbor
19
20 # Left to right pass: ensure each child with higher rating than left neighbor gets more candy
21 for i in range(1, n):
22 if ratings[i] > ratings[i - 1]:
23 left_to_right[i] = left_to_right[i - 1] + 1
24
25 # Right to left pass: ensure each child with higher rating than right neighbor gets more candy
26 for i in range(n - 2, -1, -1):
27 if ratings[i] > ratings[i + 1]:
28 right_to_left[i] = right_to_left[i + 1] + 1
29
30 # Take maximum of both requirements at each position to satisfy both constraints
31 total_candies = sum(max(left_candies, right_candies)
32 for left_candies, right_candies in zip(left_to_right, right_to_left))
33
34 return total_candies
35
1class Solution {
2 public int candy(int[] ratings) {
3 int n = ratings.length;
4
5 // Array to store minimum candies needed considering left neighbor constraint
6 int[] leftToRight = new int[n];
7 // Array to store minimum candies needed considering right neighbor constraint
8 int[] rightToLeft = new int[n];
9
10 // Initialize both arrays with 1 (minimum candy per child)
11 Arrays.fill(leftToRight, 1);
12 Arrays.fill(rightToLeft, 1);
13
14 // Left to right pass: ensure higher rated child gets more candy than left neighbor
15 for (int i = 1; i < n; i++) {
16 if (ratings[i] > ratings[i - 1]) {
17 leftToRight[i] = leftToRight[i - 1] + 1;
18 }
19 }
20
21 // Right to left pass: ensure higher rated child gets more candy than right neighbor
22 for (int i = n - 2; i >= 0; i--) {
23 if (ratings[i] > ratings[i + 1]) {
24 rightToLeft[i] = rightToLeft[i + 1] + 1;
25 }
26 }
27
28 // Calculate total candies needed
29 int totalCandies = 0;
30 for (int i = 0; i < n; i++) {
31 // Take maximum to satisfy both left and right neighbor constraints
32 totalCandies += Math.max(leftToRight[i], rightToLeft[i]);
33 }
34
35 return totalCandies;
36 }
37}
38
1class Solution {
2public:
3 int candy(vector<int>& ratings) {
4 int n = ratings.size();
5
6 // Initialize arrays to track candy count from left and right perspectives
7 // Each child starts with at least 1 candy
8 vector<int> leftToRight(n, 1);
9 vector<int> rightToLeft(n, 1);
10
11 // First pass: left to right
12 // If current child has higher rating than previous child,
13 // give them one more candy than the previous child
14 for (int i = 1; i < n; ++i) {
15 if (ratings[i] > ratings[i - 1]) {
16 leftToRight[i] = leftToRight[i - 1] + 1;
17 }
18 }
19
20 // Second pass: right to left
21 // If current child has higher rating than next child,
22 // give them one more candy than the next child
23 for (int i = n - 2; i >= 0; --i) {
24 if (ratings[i] > ratings[i + 1]) {
25 rightToLeft[i] = rightToLeft[i + 1] + 1;
26 }
27 }
28
29 // Calculate total candies needed
30 // Take maximum of left and right requirements for each child
31 // to satisfy both neighboring constraints
32 int totalCandies = 0;
33 for (int i = 0; i < n; ++i) {
34 totalCandies += max(leftToRight[i], rightToLeft[i]);
35 }
36
37 return totalCandies;
38 }
39};
40
1/**
2 * Distributes minimum number of candies to children based on ratings
3 * Each child must receive at least one candy
4 * Children with higher ratings get more candies than their neighbors
5 *
6 * @param ratings - Array of children's ratings
7 * @returns Minimum number of candies needed
8 */
9function candy(ratings: number[]): number {
10 const n: number = ratings.length;
11
12 // Track minimum candies needed based on left neighbor comparison
13 const leftToRight: number[] = new Array(n).fill(1);
14
15 // Track minimum candies needed based on right neighbor comparison
16 const rightToLeft: number[] = new Array(n).fill(1);
17
18 // First pass: left to right
19 // If current child has higher rating than left neighbor, give one more candy
20 for (let i: number = 1; i < n; i++) {
21 if (ratings[i] > ratings[i - 1]) {
22 leftToRight[i] = leftToRight[i - 1] + 1;
23 }
24 }
25
26 // Second pass: right to left
27 // If current child has higher rating than right neighbor, give one more candy
28 for (let i: number = n - 2; i >= 0; i--) {
29 if (ratings[i] > ratings[i + 1]) {
30 rightToLeft[i] = rightToLeft[i + 1] + 1;
31 }
32 }
33
34 // Calculate total candies needed
35 // Take maximum of both passes to satisfy both left and right neighbor constraints
36 let totalCandies: number = 0;
37 for (let i: number = 0; i < n; i++) {
38 totalCandies += Math.max(leftToRight[i], rightToLeft[i]);
39 }
40
41 return totalCandies;
42}
43
Time and Space Complexity
Time Complexity: O(n)
The algorithm consists of three main parts:
- First loop: Iterates through the array once from left to right (
i
from1
ton-1
), performing constant time operations at each step -O(n)
- Second loop: Iterates through the array once from right to left (
i
fromn-2
to0
), performing constant time operations at each step -O(n)
- Final sum calculation: Uses
zip
andmax
to iterate through both arrays once, taking the maximum of corresponding elements and summing them -O(n)
Total time complexity: O(n) + O(n) + O(n) = O(n)
Space Complexity: O(n)
The algorithm uses:
left
array: storesn
elements -O(n)
right
array: storesn
elements -O(n)
- The
zip
operation creates an iterator (not additional storage) - Variables
n
and loop counteri
-O(1)
Total space complexity: O(n) + O(n) + O(1) = O(n)
Where n
is the length of the ratings array.
Common Pitfalls
Pitfall 1: Using a Single Array Instead of Two Separate Arrays
The Problem: A common mistake is trying to optimize space by using a single array and updating it in place during both passes. This approach fails because the second pass can overwrite values needed for correct calculation.
Incorrect Approach:
candies = [1] * n
# Left to right pass
for i in range(1, n):
if ratings[i] > ratings[i-1]:
candies[i] = candies[i-1] + 1
# Right to left pass - THIS IS WRONG!
for i in range(n-2, -1, -1):
if ratings[i] > ratings[i+1]:
candies[i] = candies[i+1] + 1 # Overwrites left-to-right values!
Why It Fails:
For ratings like [1, 2, 3, 2, 1]
, after the left pass you get [1, 2, 3, 1, 1]
. During the right pass, when you update position 3, you set it to 2 (1+1), but you lose the information that position 2 needs 3 candies from the left pass. The final result would be incorrect.
The Solution: Use two separate arrays as shown in the original solution, or if using a single array, modify the right-to-left pass to take the maximum:
candies = [1] * n
# Left to right pass
for i in range(1, n):
if ratings[i] > ratings[i-1]:
candies[i] = candies[i-1] + 1
# Right to left pass with maximum check
for i in range(n-2, -1, -1):
if ratings[i] > ratings[i+1]:
candies[i] = max(candies[i], candies[i+1] + 1)
Pitfall 2: Confusing Strict Inequality with Non-Strict Comparison
The Problem:
Using >=
instead of >
when comparing ratings, or incorrectly handling equal ratings.
Incorrect Logic:
if ratings[i] >= ratings[i-1]: # WRONG - equal ratings don't require more candies left_to_right[i] = left_to_right[i-1] + 1
Why It Fails:
The problem states that only children with higher ratings than neighbors need more candies. Children with equal ratings to their neighbors can have any relative candy amounts. Using >=
would unnecessarily increase the candy count.
The Solution:
Always use strict inequality (>
) when comparing ratings. Children with equal ratings don't have constraints relative to each other, which helps minimize the total candy count.
Which algorithm is best for finding the shortest distance between two points in an unweighted graph?
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
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
Want a Structured Path to Master System Design Too? Donβt Miss This!