Candy
HardPrompt
There are n children standing in a line. Each child is assigned a rating value given in the integer array ratings.
You are giving candies to these children subjected to the following requirements:
- Each child must have at least one candy.
- Children with a higher rating get more candies than their neighbors.
Return the minimum number of candies you need to have to distribute the candies to the children.
Example 1:
Input: ratings = [1,0,2]
Output: 5
Explanation: You can allocate to the first, second and third child with 2, 1, 2 candies respectively.Example 2:
Input: ratings = [1,2,2]
Output: 4
Explanation: You can allocate to the first, second and third child with 1, 2, 1 candies respectively.
The third child gets 1 candy because it satisfies the above two conditions.
Constraints:
n == ratings.length1 <= n <= 2 * 1040 <= ratings[i] <= 2 * 104
Approaches
3 approaches with complexity analysis and trade-offs.
This approach directly simulates the process of satisfying the candy distribution rules. We start by giving each child one candy. Then, we make multiple passes over the children, correcting the number of candies whenever a rule is violated. We continue this process until a full pass occurs with no corrections needed, at which point we have found a stable and valid distribution.
Algorithm
- Initialize a
candiesarray of sizenwith all elements set to 1. This ensures every child gets at least one candy. - Use a boolean flag,
hasChanged, to track if any updates are made in a pass through the array. - Repeatedly loop through the
candiesarray as long ashasChangedis true. - In each pass:
- Reset
hasChangedtofalse. - Iterate through the children from left to right.
- For each child
i, check the two conditions:- If
ratings[i] > ratings[i-1]andcandies[i] <= candies[i-1], updatecandies[i] = candies[i-1] + 1and sethasChanged = true. - If
ratings[i] > ratings[i+1]andcandies[i] <= candies[i+1], updatecandies[i] = candies[i+1] + 1and sethasChanged = true.
- If
- Reset
- The loop terminates when a full pass is completed with no changes to the
candiesarray, which means all conditions are satisfied. - Finally, sum up all the values in the
candiesarray to get the minimum total.
Walkthrough
The brute-force method involves initializing a candies array of the same size as ratings, with each child receiving one candy. We then enter a loop that continues as long as we are making changes to the candy allocation. Inside the loop, we iterate through each child and check if their candy allocation violates the rules with respect to their left and right neighbors. If a child has a higher rating than a neighbor but not more candies, we increment their candy count. This process is repeated until an entire pass over all children results in no changes, indicating that the rules are satisfied for everyone. The worst-case scenario for this approach is a long, strictly decreasing sequence of ratings, which would require O(n) passes, with each pass taking O(n) time.
public class Solution { public int candy(int[] ratings) { int n = ratings.length; int[] candies = new int[n]; Arrays.fill(candies, 1); boolean hasChanged = true; while (hasChanged) { hasChanged = false; for (int i = 0; i < n; i++) { if (i > 0 && ratings[i] > ratings[i - 1] && candies[i] <= candies[i - 1]) { candies[i] = candies[i - 1] + 1; hasChanged = true; } if (i < n - 1 && ratings[i] > ratings[i + 1] && candies[i] <= candies[i + 1]) { candies[i] = candies[i + 1] + 1; hasChanged = true; } } } int totalCandies = 0; for (int candy : candies) { totalCandies += candy; } return totalCandies; }}Complexity
Time
O(n^2)
Space
O(n)
Trade-offs
Pros
Simple to understand and conceptualize.
Cons
Very inefficient, with a time complexity of O(n^2) in the worst case.
Likely to result in a 'Time Limit Exceeded' error on larger inputs.
A more efficient approach is to break the problem into two simpler subproblems. The number of candies a child receives depends on their neighbors on both the left and the right. We can satisfy these two sets of constraints in two separate passes over the ratings array.
Algorithm
- Create an integer array
candiesof sizenand initialize all its elements to 1. - First Pass (Left to Right): Traverse the
ratingsarray fromi = 1ton-1.- If
ratings[i] > ratings[i-1], it means childimust have more candies than childi-1. Updatecandies[i] = candies[i-1] + 1. - This pass ensures that the condition regarding the left neighbor is satisfied for all children.
- If
- Second Pass (Right to Left): Traverse the
ratingsarray fromi = n-2down to0.- If
ratings[i] > ratings[i+1], childimust have more candies than childi+1. The current valuecandies[i]already satisfies the left-neighbor rule. To satisfy the right-neighbor rule as well, we take the maximum:candies[i] = Math.max(candies[i], candies[i+1] + 1).
- If
- Summation: Calculate the sum of all elements in the
candiesarray to get the total minimum candies required.
Walkthrough
This approach uses a single candies array and two passes to solve the problem efficiently.
First, we do a left-to-right pass. We initialize everyone with one candy. Then, we iterate from the second child to the last. If a child's rating is higher than their left neighbor's, we give them one more candy than their left neighbor. This ensures the rule ratings[i] > ratings[i-1] => candies[i] > candies[i-1] is met.
However, this pass doesn't consider the right neighbor. For example, for ratings = [1, 0, 2], the first pass would result in candies = [1, 1, 2]. This is incorrect because the first child has a higher rating than the second but does not have more candies.
To fix this, we perform a second, right-to-left pass. We iterate from the second-to-last child to the first. If a child's rating is higher than their right neighbor's, we update their candy count to be the maximum of its current value and one more than their right neighbor's candy count. This ensures the right-neighbor rule is also satisfied without violating the already-satisfied left-neighbor rule. After both passes, the candies array holds the minimum required candies for each child.
public class Solution { public int candy(int[] ratings) { int n = ratings.length; int[] candies = new int[n]; Arrays.fill(candies, 1); // Left to right pass for (int i = 1; i < n; i++) { if (ratings[i] > ratings[i - 1]) { candies[i] = candies[i - 1] + 1; } } // Right to left pass for (int i = n - 2; i >= 0; i--) { if (ratings[i] > ratings[i + 1]) { candies[i] = Math.max(candies[i], candies[i + 1] + 1); } } int totalCandies = 0; for (int candy : candies) { totalCandies += candy; } return totalCandies; }}Complexity
Time
O(n)
Space
O(n)
Trade-offs
Pros
Efficient with a linear time complexity of O(n).
Much faster than the brute-force approach and will pass for large inputs.
The logic is straightforward to implement.
Cons
Requires extra space of O(n) for the
candiesarray.
The most optimal solution involves a single pass over the ratings array and uses constant extra space. This approach calculates the total number of candies on the fly by analyzing the local trends in ratings, specifically by identifying ascending and descending slopes.
Algorithm
- The core idea is to view the
ratingsarray as a sequence of hills (an ascent followed by a descent) and valleys. - We iterate through the array once, keeping track of the length of the current
upslope anddownslope. - Initialize
total_candies = 1,up = 0,down = 0, andpeak = 0. - Iterate from the second child (
i = 1) to the end:- Uphill (
ratings[i] > ratings[i-1]): We are on an ascending slope. Resetdownslope length, incrementupslope length. The current child getsup + 1candies. Update thepeaklength and add1 + upto the total. - Plateau (
ratings[i] == ratings[i-1]): The slope is broken. Resetup,down, andpeak. The current child gets 1 candy. Add 1 to the total. - Downhill (
ratings[i] < ratings[i-1]): We are on a descending slope. Resetupslope length, incrementdownslope length. The current child gets 1 candy. The children on the descending slope get1, 2, ..., downcandies. The peak of the hill might need an extra candy if thedownslope is longer than theupslope that preceded it. We adddownto the total, and also add 1 extra for the peak ifdown > peak.
- Uphill (
Walkthrough
This advanced approach avoids any auxiliary arrays by cleverly calculating the sum in one go. We iterate through the ratings, treating them as a landscape of hills and valleys. We maintain counters for the length of the current upward slope (up) and downward slope (down).
When the slope is ascending, the candies required form an arithmetic progression 1, 2, 3, .... When the slope is descending, they also form a progression, but the peak where the ascent meets the descent requires special handling. The number of candies at the peak must be greater than both its neighbors. This means the peak's candy count is determined by the length of both the preceding up slope and the succeeding down slope.
The algorithm iterates once, and for each element, it decides the number of candies based on the current trend (uphill, downhill, or plateau) and the lengths of the slopes, adjusting the total sum accordingly. The key insight is that for a downhill slope, we not only add candies for the current child but may also need to retroactively add a candy to the peak of the hill if the downhill slope becomes longer than the uphill one.
public class Solution { public int candy(int[] ratings) { if (ratings.length == 0) { return 0; } int totalCandies = 1; int up = 0, down = 0, peak = 0; for (int i = 1; i < ratings.length; i++) { if (ratings[i - 1] < ratings[i]) { up++; peak = up; down = 0; totalCandies += 1 + up; } else if (ratings[i - 1] == ratings[i]) { peak = up = down = 0; totalCandies += 1; } else { // ratings[i-1] > ratings[i] up = 0; down++; // Add 1 for the current child, and 1 for each of the previous `down-1` children on the slope. // Also, add 1 for the peak if the downhill slope is longer than the uphill one. totalCandies += down + (peak >= down ? 0 : 1); } } return totalCandies; }}Complexity
Time
O(n)
Space
O(1)
Trade-offs
Pros
Extremely efficient in terms of both time and space.
Optimal solution with O(n) time and O(1) space complexity.
Cons
The logic is complex and can be difficult to reason about and implement correctly.
Prone to off-by-one errors due to the tricky conditions at peaks and valleys.
Solutions
Solution
public class Solution { public int Candy(int[] ratings) { int n = ratings.Length; int[] left = new int[n]; int[] right = new int[n]; Array.Fill(left, 1); Array.Fill(right, 1); for (int i = 1; i < n; ++i) { if (ratings[i] > ratings[i - 1]) { left[i] = left[i - 1] + 1; } } for (int i = n - 2; i >= 0; --i) { if (ratings[i] > ratings[i + 1]) { right[i] = right[i + 1] + 1; } } int ans = 0; for (int i = 0; i < n; ++i) { ans += Math.Max(left[i], right[i]); } return ans; }}class Solution {public int candy(int[] ratings) { int n = ratings.length; int up = 0; int down = 0; int peak = 0; int candies = 1; for (int i = 1; i < n; i++) { if (ratings[i - 1] < ratings[i]) { up++; peak = up + 1; down = 0; candies += peak; } else if (ratings[i] == ratings[i - 1]) { peak = 0; up = 0; down = 0; candies++; } else { down++; up = 0; candies += down + (peak > down ? 0 : 1); } } return candies; }}class Solution { public: int candy ( vector < int >& ratings ) { int n = ratings . size (); vector < int > left ( n , 1 ); vector < int > right ( n , 1 ); for ( int i = 1 ; i < n ; ++ i ) { if ( ratings [ i ] > ratings [ i - 1 ]) { left [ i ] = left [ i - 1 ] + 1 ; } } for ( int i = n - 2 ; ~ i ; -- i ) { if ( ratings [ i ] > ratings [ i + 1 ]) { right [ i ] = right [ i + 1 ] + 1 ; } } int ans = 0 ; for ( int i = 0 ; i < n ; ++ i ) { ans += max ( left [ i ], right [ i ]); } return ans ; } };class Solution: def candy(self, ratings: List[int]) -> int: n = len(ratings) left = [1] * n right = [1] * n for i in range(1, n): if ratings[i] > ratings[i - 1]: left[i] = left[i - 1] + 1 for i in range(n - 2, - 1, - 1): if ratings[i] > ratings[i + 1]: right[i] = right[i + 1] + 1 return sum(max(a, b) for a, b in zip(left, right))Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.