135. Candy
Problem Description
In this LeetCode problem, we are tasked with distributing candies to children in a line based on their ratings. Each child in the line has a rating value, and our goal is to allocate candies following two rules:
- Every child must receive at least one candy.
- A child with a higher rating than their immediate neighbors must receive more candies.
We are required to determine the minimum number of candies that we need to distribute to satisfy the above rules.
Intuition
To solve this problem, we can consider the two rules that govern how we should distribute candies. The first rule is straightforward: we can start by giving each child one candy. The second rule requires a bit more thought because we need to ensure that each child with a higher rating than their neighbor receives more candies.
One approach is to evaluate how each child compares to their neighbors from both the left and right sides separately, using two arrays, left
and right
. Here’s our process for arriving at the solution:
- We create two arrays,
left
andright
, with the same length as theratings
array, and initialize all elements to 1, which accounts for the first rule that every child receives at least one candy. - We scan through the
ratings
array from left to right, comparing each child's rating to their previous neighbor. If a child's rating is higher than that of the previous child, we increment the value in the correspondingleft
array to one plus the value for the previous child. - Next, we'll need to consider the
right
neighbors, too. We scan through theratings
array from right to left this time. If we find that a child's rating is higher than that of the next child, we increment theright
array's corresponding value to one plus the value for the next child. - We then take the maximum value between the corresponding elements in
left
andright
for each child. This step ensures that the requirements for both neighbors are satisfied. - Lastly, we sum up the maximum values that we've obtained for each child, giving us the minimum number of candies needed to distribute to all the children while meeting the rules.
This algorithm effectively caters to all the possible scenarios and ensures that each child gets the correct number of candies based on their ratings and allows us to calculate the minimum total number of candies required.
Learn more about Greedy patterns.
Solution Approach
The solution can be broken down into a few steps using simple algorithms and data structures pattern:
-
Initial Setup: Two arrays named
left
andright
of the same length asratings
are created. Both are filled with1
s, which ensures that each child gets at least one candy, adhering to the first rule. -
Left-to-Right Pass: Starting from the second child (at index 1), we iterate over the
ratings
array. If we find that the current child has a higher rating than the one to the left (at indexi-1
), we give the current child more candies than the left one by incrementing theleft[i]
value toleft[i-1] + 1
.for i in range(1, len(ratings)): if ratings[i] > ratings[i - 1]: left[i] = left[i - 1] + 1
-
Right-to-Left Pass: In the next step, we start from the second-to-last child and go back to the first child. Similar to the left-to-right pass, if the current child has a higher rating than the one to the right (at index
i+1
), we perform a similar increment on theright[i]
.for i in range(len(ratings) - 2, -1, -1): if ratings[i] > ratings[i + 1]: right[i] = right[i + 1] + 1
-
Combining Results: Now that we have gone through the ratings and considered each child in comparison to their left and right neighbors, we need to combine results. The final candy count for each child should be the maximum value out of
left[i]
andright[i]
. This is because a child's final candy count must satisfy both its left and right neighbors.total_candies = sum(max(left[i], right[i]) for i in range(len(ratings)))
-
Final Result: The sum of the maximum of the
left
andright
values for each child gives us the minimum number of candies we need to distribute.
By using two arrays, we are applying a form of Dynamic Programming where we store intermediate results ("the number of candies needed considering the left or right neighbor") to solve overlapping subproblems ("the number of candies a child should get"). The solution is efficient both in terms of time and space complexity and ensures that we adhere to the problem constraints while obtaining the correct minimum total of candies.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach described above.
Consider the ratings array [1, 0, 2]
. We need to distribute candies to the children in such a way that all conditions are met.
-
Initial Setup: We initialize the
left
andright
arrays to[1, 1, 1]
because each child must get at least one candy according to the first rule. -
Left-to-Right Pass: We iterate through the
ratings
array starting from the second element. Comparing each element with its left neighbor:- For the second child (rating
0
), since0 < 1
, we do not changeleft[1]
. - For the third child (rating
2
), since2 > 0
, we incrementleft[2]
toleft[1] + 1
, soleft
becomes[1, 1, 2]
.
- For the second child (rating
-
Right-to-Left Pass: We iterate from right to left, starting from the second-to-last element:
- For the second child (rating
0
), since0 < 2
, we incrementright[1]
toright[2] + 1
, soright
becomes[1, 2, 1]
. - For the first child (rating
1
), since1 > 0
, no change is needed asright[0]
is already greater thanright[1]
.
- For the second child (rating
-
Combining Results: We take the maximum value for each index between
left
andright
.- The combined array will be
max([1,1],[1,2])
,max([1,2],[1,1])
,max([2,1],[2,1])
, which results in[1, 2, 2]
.
- The combined array will be
-
Final Result: We sum up the values of the combined array:
1 + 2 + 2 = 5
. Therefore, we need a minimum of 5 candies to distribute to the children following the rules.
By following this step-by-step process for each child's rating, we can satisfy both conditions and compute the minimum number of candies required, which in this example is 5.
Solution Implementation
1from typing import List
2
3class Solution:
4 def candy(self, ratings: List[int]) -> int:
5 # Length of the ratings list
6 num_children = len(ratings)
7
8 # Initialize lists to represent the minimum candies for each child from left and right perspectives
9 candies_from_left = [1] * num_children
10 candies_from_right = [1] * num_children
11
12 # Calculate the minimum candies required from the left perspective
13 for i in range(1, num_children):
14 # If the current child has a higher rating than the previous child,
15 # give one more candy than the previous child
16 if ratings[i] > ratings[i - 1]:
17 candies_from_left[i] = candies_from_left[i - 1] + 1
18
19 # Calculate the minimum candies required from the right perspective
20 for i in range(num_children - 2, -1, -1):
21 # If the current child has a higher rating than the next child,
22 # give one more candy than the next child
23 if ratings[i] > ratings[i + 1]:
24 candies_from_right[i] = candies_from_right[i + 1] + 1
25
26 # Sum the max number of candies required from both perspectives for each child
27 # to ensure all conditions are met
28 total_candies = sum(max(candies_left, candies_right) for candies_left, candies_right in zip(candies_from_left, candies_from_right))
29
30 return total_candies
31
1class Solution {
2 public int candy(int[] ratings) {
3 int n = ratings.length; // Total number of children
4 int increasingCount = 0; // Counter for increasing ratings
5 int decreasingCount = 0; // Counter for decreasing ratings
6 int peakCandy = 0; // Number of candies at the peak (highest point in the current iteration)
7 int totalCandies = 1; // Start with one candy for the first child
8
9 // Iterate through each child starting from the second one
10 for (int i = 1; i < n; i++) {
11 if (ratings[i - 1] < ratings[i]) {
12 // Rating is higher than the previous child's rating
13 increasingCount++;
14 peakCandy = increasingCount + 1; // The current child gets one more candy than the increasing count
15 decreasingCount = 0; // Reset decreasing count
16 totalCandies += peakCandy; // Update total candies with the current child's candies
17 } else if (ratings[i] == ratings[i - 1]) {
18 // Rating is equal to the previous child's rating
19 // Reset the peak, increasing count, and decreasing count
20 peakCandy = 0;
21 increasingCount = 0;
22 decreasingCount = 0;
23 totalCandies++; // Each child gets at least one candy
24 } else {
25 // Rating is lower than the previous child's rating
26 decreasingCount++;
27 increasingCount = 0; // Reset increasing count
28 // Add the number of candies for decreasing sequence.
29 // If peak candy count is not enough to cover the decreasing sequence, an extra candy is given to the peak
30 totalCandies += decreasingCount + (peakCandy > decreasingCount ? 0 : 1);
31 }
32 }
33
34 // Return the computed total number of candies needed
35 return totalCandies;
36 }
37}
38
1#include <vector>
2#include <algorithm> // Included to use std::max
3
4class Solution {
5public:
6 /**
7 * Calculate the minimum number of candies to distribute to children,
8 * given different ratings, where each child must have at least one candy,
9 * and children with a higher rating than their neighbors must have more candies.
10 *
11 * @param ratings A vector of integers where each element represents the rating of a child.
12 * @return The minimum number of candies required.
13 */
14 int candy(vector<int>& ratings) {
15 // Get the number of children
16 int numChildren = ratings.size();
17
18 // Initialize left and right vectors with 1 candy for each child
19 vector<int> candiesFromLeft(numChildren, 1);
20 vector<int> candiesFromRight(numChildren, 1);
21
22 // Distribute candies from left to right, ensuring each child with a higher rating
23 // than the left neighbor gets more candies
24 for (int i = 1; i < numChildren; ++i) {
25 if (ratings[i] > ratings[i - 1]) {
26 candiesFromLeft[i] = candiesFromLeft[i - 1] + 1;
27 }
28 }
29
30 // Distribute candies from right to left with the same logic
31 for (int i = numChildren - 2; i >= 0; --i) {
32 if (ratings[i] > ratings[i + 1]) {
33 candiesFromRight[i] = candiesFromRight[i + 1] + 1;
34 }
35 }
36
37 // Calculate the total amount of candies by taking the maximum of
38 // candies from left and right at each position
39 int totalCandies = 0;
40 for (int i = 0; i < numChildren; ++i) {
41 totalCandies += std::max(candiesFromLeft[i], candiesFromRight[i]);
42 }
43
44 // Return the total number of candies needed
45 return totalCandies;
46 }
47};
48
1function candy(ratings: number[]): number {
2 const numChildren = ratings.length;
3 const candiesFromLeft = new Array(numChildren).fill(1);
4 const candiesFromRight = new Array(numChildren).fill(1);
5
6 // Traverse from left to right, and assign candies ensuring that each child
7 // with a higher rating than the left neighbor receives more candies
8 for (let i = 1; i < numChildren; ++i) {
9 if (ratings[i] > ratings[i - 1]) {
10 candiesFromLeft[i] = candiesFromLeft[i - 1] + 1;
11 }
12 }
13
14 // Traverse from right to left, and assign candies ensuring that each child
15 // with a higher rating than the right neighbor receives more candies
16 for (let i = numChildren - 2; i >= 0; --i) {
17 if (ratings[i] > ratings[i + 1]) {
18 candiesFromRight[i] = candiesFromRight[i + 1] + 1;
19 }
20 }
21
22 // Calculate the minimum required candies by selecting the maximum number
23 // of candies assigned from either direction for each child
24 let totalCandies = 0;
25 for (let i = 0; i < numChildren; ++i) {
26 totalCandies += Math.max(candiesFromLeft[i], candiesFromRight[i]);
27 }
28
29 // Return the total number of candies needed
30 return totalCandies;
31}
32
Time and Space Complexity
The time complexity of the code is O(n)
. Here's the analysis:
- There are two single-pass for-loops that go through the list of
ratings
which containsn
elements. Each loop runs inO(n)
and they are run sequentially, not nested. So the time complexity remainsO(n)
. - The final return statement uses a generator expression with
zip
to combine the two lists (left
andright
) and sum the maximum values of corresponding elements.zip
function pairs up the elements of both lists, which takesO(n)
time, and the summation also takesO(n)
time since each element is considered once.
Combining these linear operations still results in a total time complexity of O(n)
.
The space complexity of the code is O(n)
. This is due to:
- Two additional lists
left
andright
, each of sizen
, are created to store the incrementing sequences based on theratings
. - No other additional space that scales with the input size is used, so the total space complexity is the sum of the space used by these two lists, which is
2 * O(n)
simplified toO(n)
as constants are dropped in big O notation.
Learn more about how to find time and space complexity quickly using problem constraints.
What does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
1function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
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
LeetCode 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 we
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!