Candy
Description
There are n children standing in a line. Each child has been assigned a rating value, represented by the integer array ratings.
You need to distribute candies to these children following two rules:
- Every child must receive at least one candy.
- A child with a higher rating than an immediate neighbor must receive more candies than that neighbor.
Return the minimum total number of candies you need to distribute to all the children while satisfying both rules.
Note that the comparison is strict — children with equal ratings do not need to have equal (or ordered) candy counts relative to each other.
Examples
Example 1
Input: ratings = [1, 0, 2]
Output: 5
Explanation:
- Child 0 has rating 1, which is higher than child 1's rating 0. So child 0 must get more candies than child 1.
- Child 2 has rating 2, which is higher than child 1's rating 0. So child 2 must get more candies than child 1.
- Child 1 has the lowest rating, so they can receive the minimum of 1 candy.
- A valid minimum distribution is [2, 1, 2], totaling 2 + 1 + 2 = 5 candies.
Example 2
Input: ratings = [1, 2, 2]
Output: 4
Explanation:
- Child 1 has rating 2, which is higher than child 0's rating 1. So child 1 must get more candies than child 0.
- Child 2 also has rating 2, but this is equal to child 1's rating (not higher). The rule only applies to strictly higher ratings, so child 2 can receive just 1 candy.
- A valid minimum distribution is [1, 2, 1], totaling 1 + 2 + 1 = 4 candies.
Example 3
Input: ratings = [1, 3, 2, 2, 1]
Output: 7
Explanation:
- Child 1 (rating 3) is higher than both neighbors (ratings 1 and 2), so child 1 needs more candies than both.
- Child 3 (rating 2) is higher than child 4 (rating 1), so child 3 needs more candies than child 4.
- Children 2 and 3 have equal ratings, so no ordering is required between them.
- A valid minimum distribution is [1, 2, 1, 2, 1], totaling 1 + 2 + 1 + 2 + 1 = 7 candies.
Constraints
- n == ratings.length
- 1 ≤ n ≤ 2 × 10^4
- 0 ≤ ratings[i] ≤ 2 × 10^4
Editorial
Brute Force
Intuition
The most straightforward way to think about this problem is: give every child 1 candy to start, then repeatedly scan through the line looking for violations of the rules. Whenever we find a child whose rating is higher than a neighbor but who doesn't have more candies, we increase that child's candy count by 1.
Imagine you're a teacher walking back and forth along a line of students. Each time you walk past a student who deserves more candy than their neighbor but doesn't have enough, you hand them one extra candy. You keep walking back and forth until you make a full pass with no corrections needed.
This approach is simple to understand but inefficient — in the worst case (like a strictly increasing or decreasing sequence), you might need to scan the entire array many times before everything is settled.
Step-by-Step Explanation
Let's trace through with ratings = [1, 3, 2, 2, 1]:
Initial state: candies = [1, 1, 1, 1, 1]
Pass 1 (left to right + right to left scan):
Step 1: Compare child 0 (rating 1) with child 1 (rating 3). Child 1 has a higher rating but equal candies (1 vs 1). Violation! Increase child 1's candies to 2.
- candies = [1, 2, 1, 1, 1]
Step 2: Compare child 1 (rating 3) with child 2 (rating 2). Child 1 already has more candies (2 > 1). No violation.
Step 3: Compare child 2 (rating 2) with child 3 (rating 2). Equal ratings — no constraint between them.
Step 4: Compare child 3 (rating 2) with child 4 (rating 1). Child 3 has a higher rating but equal candies (1 vs 1). Violation! Increase child 3's candies to 2.
- candies = [1, 2, 1, 2, 1]
Step 5: Check reverse: child 4 vs child 3, child 3 vs child 2, etc. No new violations found.
Pass 2: Scan again. No violations found anywhere — we're done!
Result: candies = [1, 2, 1, 2, 1], total = 7
Brute Force — Iterative Violation Fixing — Watch how we repeatedly scan the candy array, fixing violations one at a time until the distribution satisfies all constraints.
Algorithm
- Initialize a candy array of size n, with every entry set to 1.
- Set a flag
changedto true. - While
changedis true:
a. Setchangedto false.
b. For each child i from 0 to n-1:- If ratings[i] > ratings[i-1] and candies[i] ≤ candies[i-1], set candies[i] = candies[i-1] + 1 and mark
changed= true. - If ratings[i] > ratings[i+1] and candies[i] ≤ candies[i+1], set candies[i] = candies[i+1] + 1 and mark
changed= true.
- If ratings[i] > ratings[i-1] and candies[i] ≤ candies[i-1], set candies[i] = candies[i-1] + 1 and mark
- Return the sum of the candy array.
Code
class Solution {
public:
int candy(vector<int>& ratings) {
int n = ratings.size();
vector<int> candies(n, 1);
bool changed = true;
while (changed) {
changed = false;
for (int i = 0; i < n; i++) {
// Check left neighbor
if (i > 0 && ratings[i] > ratings[i - 1]
&& candies[i] <= candies[i - 1]) {
candies[i] = candies[i - 1] + 1;
changed = true;
}
// Check right neighbor
if (i < n - 1 && ratings[i] > ratings[i + 1]
&& candies[i] <= candies[i + 1]) {
candies[i] = candies[i + 1] + 1;
changed = true;
}
}
}
int total = 0;
for (int c : candies) total += c;
return total;
}
};class Solution:
def candy(self, ratings: list[int]) -> int:
n = len(ratings)
candies = [1] * n
changed = True
while changed:
changed = False
for i in range(n):
# Check left neighbor
if i > 0 and ratings[i] > ratings[i - 1] \
and candies[i] <= candies[i - 1]:
candies[i] = candies[i - 1] + 1
changed = True
# Check right neighbor
if i < n - 1 and ratings[i] > ratings[i + 1] \
and candies[i] <= candies[i + 1]:
candies[i] = candies[i + 1] + 1
changed = True
return sum(candies)class Solution {
public int candy(int[] ratings) {
int n = ratings.length;
int[] candies = new int[n];
Arrays.fill(candies, 1);
boolean changed = true;
while (changed) {
changed = false;
for (int i = 0; i < n; i++) {
// Check left neighbor
if (i > 0 && ratings[i] > ratings[i - 1]
&& candies[i] <= candies[i - 1]) {
candies[i] = candies[i - 1] + 1;
changed = true;
}
// Check right neighbor
if (i < n - 1 && ratings[i] > ratings[i + 1]
&& candies[i] <= candies[i + 1]) {
candies[i] = candies[i + 1] + 1;
changed = true;
}
}
}
int total = 0;
for (int c : candies) total += c;
return total;
}
}Complexity Analysis
Time Complexity: O(n²)
In the worst case (e.g., a strictly increasing sequence like [1, 2, 3, 4, 5]), each pass only fixes one child's candy count, and we may need up to n passes. Each pass scans all n children, giving O(n) × O(n) = O(n²) total work.
Space Complexity: O(n)
We use an auxiliary candy array of size n to store each child's candy count.
Why This Approach Is Not Efficient
The brute force approach repeatedly scans the entire array, potentially requiring up to n full passes before converging. With n up to 2 × 10^4, this yields up to 4 × 10^8 operations — far too slow for typical time limits.
The core inefficiency is that each pass only propagates candy requirements by one position. In a long increasing sequence [1, 2, 3, ..., n], pass 1 fixes child 1, pass 2 fixes child 2, and so on — it takes n-1 passes to propagate the requirement across the entire sequence.
The key insight is that we can handle left-neighbor constraints and right-neighbor constraints separately in just two directed passes, rather than iterating until convergence. A left-to-right pass naturally propagates increasing requirements forward, and a right-to-left pass propagates them backward. By handling each direction in one sweep, we eliminate all the redundant re-scanning.
Better Approach - Two-Pass Greedy
Intuition
Each child's candy count is influenced by two things: how they compare to their left neighbor and how they compare to their right neighbor. The clever insight is that we can separate these two concerns.
Imagine walking through the line from left to right. As you walk, you only worry about one rule: if a child rates higher than the child on their left, give them one more candy. This single pass perfectly handles all left-neighbor constraints.
Then imagine walking back from right to left, applying the same logic for right neighbors. Each child might need more candies from the right-neighbor perspective than what the left pass gave them.
The final candy count for each child is the maximum of what the two passes require. Taking the maximum ensures both constraints are satisfied simultaneously — if the left pass says a child needs 3 candies and the right pass says 2, the child gets 3 to satisfy both rules.
This two-pass strategy processes the entire array in just two linear scans, a dramatic improvement over the brute force's repeated scanning.
Step-by-Step Explanation
Let's trace through with ratings = [1, 3, 2, 2, 1]:
Initialization:
- left = [1, 1, 1, 1, 1] (everyone starts with 1 candy)
- right = [1, 1, 1, 1, 1]
Left-to-Right Pass (handle left-neighbor constraints):
Step 1: i=1: ratings[1]=3 > ratings[0]=1? YES → left[1] = left[0] + 1 = 2
- left = [1, 2, 1, 1, 1]
Step 2: i=2: ratings[2]=2 > ratings[1]=3? NO → left[2] stays 1
- left = [1, 2, 1, 1, 1]
Step 3: i=3: ratings[3]=2 > ratings[2]=2? NO (equal, not strictly greater) → left[3] stays 1
- left = [1, 2, 1, 1, 1]
Step 4: i=4: ratings[4]=1 > ratings[3]=2? NO → left[4] stays 1
- left = [1, 2, 1, 1, 1]
Right-to-Left Pass (handle right-neighbor constraints):
Step 5: i=3: ratings[3]=2 > ratings[4]=1? YES → right[3] = right[4] + 1 = 2
- right = [1, 1, 1, 2, 1]
Step 6: i=2: ratings[2]=2 > ratings[3]=2? NO → right[2] stays 1
- right = [1, 1, 1, 2, 1]
Step 7: i=1: ratings[1]=3 > ratings[2]=2? YES → right[1] = right[2] + 1 = 2
- right = [1, 2, 1, 2, 1]
Step 8: i=0: ratings[0]=1 > ratings[1]=3? NO → right[0] stays 1
- right = [1, 2, 1, 2, 1]
Final Merge (take max at each position):
Step 9: For each child, candy[i] = max(left[i], right[i])
- Child 0: max(1, 1) = 1
- Child 1: max(2, 2) = 2
- Child 2: max(1, 1) = 1
- Child 3: max(1, 2) = 2
- Child 4: max(1, 1) = 1
- Final: [1, 2, 1, 2, 1], total = 7
Two-Pass Greedy — Left and Right Sweeps — Watch how a left-to-right pass captures left-neighbor constraints and a right-to-left pass captures right-neighbor constraints. The final candy count is the max of both passes.
Algorithm
- Create two arrays
leftandright, each of size n, initialized to 1. - Left-to-right pass: For i from 1 to n-1:
- If ratings[i] > ratings[i-1], set left[i] = left[i-1] + 1
- Right-to-left pass: For i from n-2 down to 0:
- If ratings[i] > ratings[i+1], set right[i] = right[i+1] + 1
- Merge: For each child i, their candy count is max(left[i], right[i]).
- Return the sum of all candy counts.
Code
class Solution {
public:
int candy(vector<int>& ratings) {
int n = ratings.size();
vector<int> left(n, 1), right(n, 1);
// Left to right: handle left-neighbor constraints
for (int i = 1; i < n; i++) {
if (ratings[i] > ratings[i - 1]) {
left[i] = left[i - 1] + 1;
}
}
// Right to left: handle right-neighbor constraints
for (int i = n - 2; i >= 0; i--) {
if (ratings[i] > ratings[i + 1]) {
right[i] = right[i + 1] + 1;
}
}
// Merge: take max of both constraints
int total = 0;
for (int i = 0; i < n; i++) {
total += max(left[i], right[i]);
}
return total;
}
};class Solution:
def candy(self, ratings: list[int]) -> int:
n = len(ratings)
left = [1] * n
right = [1] * n
# Left to right: handle left-neighbor constraints
for i in range(1, n):
if ratings[i] > ratings[i - 1]:
left[i] = left[i - 1] + 1
# Right to left: handle right-neighbor constraints
for i in range(n - 2, -1, -1):
if ratings[i] > ratings[i + 1]:
right[i] = right[i + 1] + 1
# Merge: take max of both constraints
return sum(max(l, r) for l, r in zip(left, right))class Solution {
public int candy(int[] ratings) {
int n = ratings.length;
int[] left = new int[n];
int[] right = new int[n];
Arrays.fill(left, 1);
Arrays.fill(right, 1);
// Left to right: handle left-neighbor constraints
for (int i = 1; i < n; i++) {
if (ratings[i] > ratings[i - 1]) {
left[i] = left[i - 1] + 1;
}
}
// Right to left: handle right-neighbor constraints
for (int i = n - 2; i >= 0; i--) {
if (ratings[i] > ratings[i + 1]) {
right[i] = right[i + 1] + 1;
}
}
// Merge: take max of both constraints
int total = 0;
for (int i = 0; i < n; i++) {
total += Math.max(left[i], right[i]);
}
return total;
}
}Complexity Analysis
Time Complexity: O(n)
We make exactly three linear passes through the array: one left-to-right, one right-to-left, and one final merge. Each pass processes each element in O(1) time, so the total time is 3 × O(n) = O(n).
Space Complexity: O(n)
We use two auxiliary arrays (left and right), each of size n, to store the candy requirements from each direction. This gives O(n) extra space.
Why This Approach Is Not Efficient
The two-pass greedy approach achieves optimal O(n) time complexity, which is excellent. However, it uses O(n) extra space for two auxiliary arrays. For n up to 2 × 10^4, this is perfectly fine in practice.
But we can ask: is it possible to solve this problem in O(1) extra space? The answer is yes. The key observation is that any valid candy distribution forms a pattern of rising slopes (where consecutive ratings increase) and falling slopes (where consecutive ratings decrease). Instead of storing the entire left-pass and right-pass arrays, we can process the input by identifying these slope segments on the fly and computing their candy contributions using simple arithmetic (the sum of consecutive integers 1 + 2 + ... + k = k(k+1)/2). This eliminates the need for any auxiliary arrays.
Optimal Approach - Slope Counting (Single Pass)
Intuition
Imagine plotting the ratings as a graph. You'll see a landscape of peaks and valleys. Each ascending slope requires 1, 2, 3, ... candies as you climb up. Each descending slope requires ..., 3, 2, 1 candies as you go down.
The trick is to count the length of each ascending and descending slope as we walk through the ratings in a single pass. For a slope of length k, the total candies for that segment is the sum of 1 + 2 + ... + k.
The tricky part is handling peaks — the child at the top of a peak participates in both the ascending and descending slopes. To avoid double-counting, we initially count the peak as part of the ascending slope, then adjust: if the descending slope turns out to be longer than the ascending slope, the peak child's candy count should match the descending slope instead (since they need more candies than both neighbors, they take the maximum).
When two adjacent children have equal ratings, the slope resets — there's no constraint between them, so the next child can start fresh with 1 candy.
Step-by-Step Explanation
Let's trace through with ratings = [1, 3, 2, 2, 1]:
We start with total = n = 5 (every child gets at least 1 candy). Then we add the extra candies for slopes.
Step 1: i=1: ratings[1]=3 > ratings[0]=1. Start ascending slope. peak = 1, total += 1 = 6.
Step 2: i=2: ratings[2]=2 < ratings[1]=3. The ascending slope ended (peak was 1). Now we start a descending slope. valley = 1, total += 1 = 7.
Step 3: At the end of this descent, check: is the descending slope (valley=1) ≥ the ascending slope (peak=1)? Yes (1 ≥ 1), so the peak child needs one extra candy for the descent side. total += 1 = 8... Wait, let me re-derive more carefully.
Actually, let me trace more carefully with the slope-counting algorithm:
total = 5 (base of 1 candy per child), i = 1
Step 1: i=1: ratings[1]=3 > ratings[0]=1 → ascending. peak=1. total += peak = total + 1 = 6. i=2.
Step 2: i=2: ratings[2]=2 < ratings[1]=3 → descending starts. valley=1. total += valley = 6 + 1 = 7. i=3.
End of descent (ratings[3]=2 is not < ratings[2]=2). The peak before this descent was 1, the valley length was 1. Since valley ≥ peak, we need to adjust: total += 0 (valley - peak + 1 would be 1, but we subtract min(peak, valley) from the peak overlap). Specifically: subtract min(peak, valley) = min(1,1) = 1...
Let me use the cleaner formulation:
- total starts at n = 5
- i = 1: equal check — ratings[1]=3 ≠ ratings[0]=1, ascending starts.
- while ascending: peak counts up. peak=1, total += 1 → total=6. i becomes 2.
- i=2: ratings[2]=2 < ratings[1]=3, ascent stops.
- while descending: valley counts up. valley=1, total += 1 → total=7. i becomes 3.
- i=3: ratings[3]=2 = ratings[2]=2, descent stops.
- Adjust for peak overlap: total -= min(peak, valley) = min(1,1) = 1 → total=6. Wait that doesn't give 7.
Let me re-examine. The standard single-pass slope algorithm actually gives:
- total = 5 initially
- Ascending slope of length 1 (child 0→1): adds 1. total = 6.
- Descending slope of length 1 (child 1→2): adds 1. total = 7.
- Peak adjustment: subtract min(1,1)=1. total = 6... Hmm that gives 6 not 7.
Wait, the adjustment formula is: the peak element gets max(peak, valley) extra candies, but in the slope sums we counted it as peak in the ascending and valley in the descending. So we subtract min(peak, valley) to avoid double-counting.
Actually for [1,3,2,2,1]: the ascending from 1→3 gives slope length 1 (just one increase). The descending from 3→2 gives slope length 1. Peak = 1, valley = 1. The ascending contributes 1+2=3 total for 2 children (well, 1 base + 1 extra for child 1). The descending contributes 1 extra for child 2. But the peak child (child 1) was counted as getting peak=1 extra in ascending. If valley > peak, the peak child needs valley extras instead. We add max(peak,valley) - peak - valley + min(peak,valley)... This is getting complex. Let me just present the clean algorithm.
OK for the text explanation, let me use a cleaner trace:
ratings = [1, 3, 2, 2, 1], total = 5 (base), i = 1
Step 1: ratings[1]=3 > ratings[0]=1: ascending slope begins. peak = 0.
Inner while: peak=1, total += 1 → total=6. i=2. ratings[2]=2 < ratings[1]=3 → stop ascending.
Step 2: ratings[2]=2 < ratings[1]=3: descending slope begins. valley = 0.
Inner while: valley=1, total += 1 → total=7. i=3. ratings[3]=2 = ratings[2]=2 → stop descending.
Step 3: Adjust peak overlap: subtract min(peak, valley) = min(1,1) = 1. total = 7 - 1 = 6. Hmm, 6 ≠ 7.
But the correct answer is 7. So let me re-check...
Actually [1, 2, 1, 2, 1] = 7. Let me verify: child ratings [1,3,2,2,1] → candies [1,2,1,2,1] → sum = 7.
With the slope method: total starts at n=5.
- Slope 1→3: one ascent step. Adds 1 extra candy (child 1 goes from base 1 to 2). total = 6.
- Slope 3→2: one descent step. Adds 1 extra candy (child 1 or child 2 needs adjustment). total = 7.
- Peak adjustment: the peak child (rating 3) was counted in both slopes. In the ascending slope it got 1 extra, in the descending slope child 2 got 1 extra. Actually child 1 is the peak. In ascending, child 1 gets 1+1=2 candies. In descending from child 1→child 2, child 2 keeps 1. The peak child is at the boundary — we included it in the ascending sum. The descending slope of length 1 adds candies for child 2 only (1 extra). So no overlap to fix!
Wait, the standard algorithm: ascending slope of length k means we add 1+2+...+k extra on top of the base. Or rather the slope contributes a total of 1+2+...+(k+1) candies for (k+1) children. Then descending slope of length k contributes 1+2+...+(k+1) for (k+1) children. The peak child is shared. So we subtract min(ascending_length, descending_length)? No, we subtract the smaller peak value.
OK this is getting complicated for me to trace inline. Let me just present the algorithm correctly and use a simpler trace.
Actually let me just present a clean version and correct trace. The total from slopes for [1,3,2,2,1]:
Segment 1: [1, 3, 2] — ascending slope of length 1 (1→3), then descending slope of length 1 (3→2).
- Ascending: extra candies = 1 (child 1 gets 1 extra above base)
- Descending: extra candies = 1 (child 2 gets... wait, child 2 gets base 1, which is fine since 2<3)
- Actually the descent means the child before the descent bottom needs more. In the slope formulation:
- Ascending slope of length 1: candies for that slope = 1+2 = 3 (for children 0,1)
- Descending slope of length 1: candies = 2+1 = 3 (for children 1,2)
- Peak overlap: child 1 counted as 2 in ascending and 2 in descending. Take max(2,2)=2. Subtract double-count = 2. So slope total = 3+3-2 = 4. For children 0,1,2.
Segment 2: [2, 1] — descending slope of length 1.
- Candies = 2+1 = 3 (for children 3,4).
- No peak before this (ratings[3]=ratings[2], so it's a flat reset).
Total = 4 + 3 = 7. Correct!
OK so total of the whole array calculated by slope segments gives 7.
Slope Counting — Single Pass O(1) Space — Watch how we identify ascending and descending slopes in a single pass, computing candy totals using arithmetic sums without storing any auxiliary arrays.
Algorithm
- Initialize total = n (each child gets at least 1 candy). Set i = 1.
- While i < n:
a. If ratings[i] == ratings[i-1], just advance i (flat segment, no extra candy).
b. Count ascending slope: While ratings[i] > ratings[i-1], incrementpeakand addpeakto total. Advance i.
c. Count descending slope: While ratings[i] < ratings[i-1], incrementvalleyand addvalleyto total. Advance i.
d. Peak adjustment: The peak child was counted in the ascending sum with valuepeak. If the descending slope is longer (valley > peak), the peak actually needsvalleycandies. Subtract min(peak, valley) from total (to remove the double-counted peak and replace with the correct value).
e. Reset peak and valley to 0. - Return total.
Code
class Solution {
public:
int candy(vector<int>& ratings) {
int n = ratings.size();
int total = n; // Each child gets at least 1
int i = 1;
while (i < n) {
// Skip equal ratings
if (ratings[i] == ratings[i - 1]) {
i++;
continue;
}
// Count ascending slope
int peak = 0;
while (i < n && ratings[i] > ratings[i - 1]) {
peak++;
total += peak;
i++;
}
// Count descending slope
int valley = 0;
while (i < n && ratings[i] < ratings[i - 1]) {
valley++;
total += valley;
i++;
}
// Peak adjustment: peak child takes max of both slopes
total -= min(peak, valley);
}
return total;
}
};class Solution:
def candy(self, ratings: list[int]) -> int:
n = len(ratings)
total = n # Each child gets at least 1
i = 1
while i < n:
# Skip equal ratings
if ratings[i] == ratings[i - 1]:
i += 1
continue
# Count ascending slope
peak = 0
while i < n and ratings[i] > ratings[i - 1]:
peak += 1
total += peak
i += 1
# Count descending slope
valley = 0
while i < n and ratings[i] < ratings[i - 1]:
valley += 1
total += valley
i += 1
# Peak adjustment: peak child takes max of both slopes
total -= min(peak, valley)
return totalclass Solution {
public int candy(int[] ratings) {
int n = ratings.length;
int total = n; // Each child gets at least 1
int i = 1;
while (i < n) {
// Skip equal ratings
if (ratings[i] == ratings[i - 1]) {
i++;
continue;
}
// Count ascending slope
int peak = 0;
while (i < n && ratings[i] > ratings[i - 1]) {
peak++;
total += peak;
i++;
}
// Count descending slope
int valley = 0;
while (i < n && ratings[i] < ratings[i - 1]) {
valley++;
total += valley;
i++;
}
// Peak adjustment: peak child takes max of both slopes
total -= Math.min(peak, valley);
}
return total;
}
}Complexity Analysis
Time Complexity: O(n)
We traverse the ratings array exactly once. Even though we have nested while loops, each element is visited at most once across all loops (the index i only moves forward). So the total work is O(n).
Space Complexity: O(1)
We use only a fixed number of variables (total, i, peak, valley) regardless of the input size. No auxiliary arrays are needed — a significant improvement over the two-pass approach's O(n) space.