HardArrays & Strings

Candy โ€” Solution

Problem

You have n children standing in a line, each assigned a rating score. Hand out candy so that every child gets at least one piece, and any child with a strictly higher rating than an adjacent neighbor must receive more candy than that neighbor. Find the minimum total number of candies needed.

  • Input: ratings = [1, 0, 2]
  • Output: 5
  • Explanation: Child 0 gets 2, child 1 gets 1, child 2 gets 2 โ€” the only valid minimum distribution.

A tricky case: ratings = [1, 2, 2] โ†’ output 4. The third child has the same rating as the second, so they can both receive just 1 candy (the constraint only triggers on strictly greater).

Intuition

Each child's candy count is constrained by both neighbors, but the left-neighbor and right-neighbor constraints are independent โ€” they never conflict with each other on the same child. That means we can satisfy each direction in its own pass, then combine. The greedy insight is that a single left-to-right scan handles all left-neighbor constraints perfectly, and a single right-to-left scan handles all right-neighbor constraints โ€” as long as we take the maximum at each position when merging.

Approach 1 โ€” Brute Force (Repeated Passes)

Initialize every child to 1 candy, then repeatedly scan the array and bump up any child whose candy count violates either neighbor constraint. Stop when a full scan produces no changes.

  1. Set candies[i] = 1 for every child.
  2. Scan left to right: if ratings[i] > ratings[i-1] but candies[i] <= candies[i-1], set candies[i] = candies[i-1] + 1 and mark that a change occurred.
  3. In the same scan, check the right neighbor with the same logic.
  4. Repeat until no changes occur in a full scan.
  5. Return the sum of candies.
1def candy(ratings: list[int]) -> int:
2    n = len(ratings)
3    candies = [1] * n
4    changed = True
5    while changed:  # keep scanning until the array fully stabilizes
6        changed = False
7        for i in range(n):
8            if i > 0 and ratings[i] > ratings[i - 1] and candies[i] <= candies[i - 1]:
9                candies[i] = candies[i - 1] + 1
10                changed = True
11            if i < n - 1 and ratings[i] > ratings[i + 1] and candies[i] <= candies[i + 1]:
12                candies[i] = candies[i + 1] + 1
13                changed = True
14    return sum(candies)

Time: O(nยฒ) โ€” a strictly decreasing input like [5,4,3,2,1] requires O(n) passes, each touching O(n) elements.

Space: O(n) โ€” the candies array.

Approach 2 โ€” Two-Pass Greedy

Do exactly two linear scans: one left-to-right that satisfies every left-neighbor constraint, then one right-to-left that satisfies every right-neighbor constraint. At each position in the second pass, take the maximum of the two passes' values so the left-pass result is never overwritten.

  1. Initialize candies[i] = 1 for all children.
  2. Left-to-right pass: if ratings[i] > ratings[i-1], set candies[i] = candies[i-1] + 1.
  3. Right-to-left pass: if ratings[i] > ratings[i+1], set candies[i] = max(candies[i], candies[i+1] + 1).
  4. Return the sum.
1def candy(ratings: list[int]) -> int:
2    n = len(ratings)
3    candies = [1] * n
4
5    for i in range(1, n):
6        if ratings[i] > ratings[i - 1]:
7            candies[i] = candies[i - 1] + 1  # must beat left neighbor
8
9    for i in range(n - 2, -1, -1):
10        if ratings[i] > ratings[i + 1]:
11            # must beat right neighbor, but also keep left-pass result if larger
12            candies[i] = max(candies[i], candies[i + 1] + 1)
13
14    return sum(candies)

Time: O(n) โ€” exactly two linear passes over the array.

Space: O(n) โ€” the candies array.

Complexity Summary

ApproachTimeSpaceWhen to use
Brute ForceO(nยฒ)O(n)Only for understanding โ€” correct but too slow for large inputs
Two-Pass GreedyO(n)O(n)Always โ€” clean, fast, and handles all edge cases

Common Mistakes

  • Overwriting the left-pass result in the right-to-left pass โ€” writing candies[i] = candies[i+1] + 1 instead of candies[i] = max(candies[i], candies[i+1] + 1) destroys the work done in the first pass, producing the wrong answer for sequences like [1,3,2,2,1].
  • Treating equal ratings as requiring equal candy โ€” the constraint says strictly higher rating requires more candy; two children with the same rating can receive different amounts, and the minimum is to give them each 1.
  • Doing only the left-to-right pass โ€” this satisfies every child's left-neighbor constraint but ignores the right neighbor, failing on any strictly decreasing suffix (e.g., [5,4,3,2,1] would incorrectly return 5 instead of 15).
  • Using >= instead of > in the comparison โ€” this would force children with equal ratings to have different candy counts, increasing the total unnecessarily and potentially producing wrong answers.
  • Starting from 0 instead of 1 โ€” every child must receive at least one candy regardless of rating, so the initial array must be all 1s, not all 0s.

Related Problems

  • Trapping Rain Water โ€” the same two-pass pattern where you compute left-max and right-max independently then combine
  • Gas Station โ€” greedy on a linear array where local decisions must satisfy a global constraint
  • Jump Game โ€” greedy array problem where the optimal decision at each step depends on prior state
  • Non-overlapping Intervals โ€” greedy minimization problem where a carefully chosen traversal order makes the problem tractable
  • Best Time to Buy and Sell Stock โ€” single-pass greedy on an array tracking a running minimum/maximum

Ready to practice? Try it on SkillFlow

Adaptive problems, AI follow-up interviews, and a skill score that shows exactly where you need to improve.

Practice This Problem โ†’