1103. Distribute Candies to People
Problem Description
You have a certain number of candies
to distribute among num_people
people standing in a row. The distribution follows a specific pattern:
Round 1:
- Person 1 gets 1 candy
- Person 2 gets 2 candies
- Person 3 gets 3 candies
- ...
- Person n gets n candies
Round 2:
- Person 1 gets n + 1 candies
- Person 2 gets n + 2 candies
- Person 3 gets n + 3 candies
- ...
- Person n gets 2n candies
Subsequent Rounds: The pattern continues with each person receiving one more candy than the previous person received in the entire distribution sequence. After reaching the last person, you go back to the first person and continue.
This process repeats until you run out of candies. When the remaining candies are not enough for the next person's full allocation, that person receives all the remaining candies.
Your task is to return an array of length num_people
where each element represents the total number of candies each person receives.
Example walkthrough:
If candies = 10
and num_people = 3
:
- Person 1 gets 1 candy (9 left)
- Person 2 gets 2 candies (7 left)
- Person 3 gets 3 candies (4 left)
- Back to Person 1: gets 4 candies (0 left)
Final distribution: [5, 2, 3]
The solution uses a simulation approach where we iterate through each distribution step, keeping track of which person's turn it is using modulo operation i % num_people
, and giving them either the expected amount i + 1
or the remaining candies, whichever is smaller.
Intuition
The key insight is recognizing that this is a cyclic distribution problem where we need to keep track of two things: which person's turn it is and how many candies they should receive.
Let's think about the pattern:
- In the first pass through all people, we give 1, 2, 3, ..., n candies
- In the second pass, we give n+1, n+2, n+3, ..., 2n candies
- The pattern continues with each candy distribution being one more than the previous
This suggests we need a counter that continuously increments. If we use a variable i
that starts at 0 and increments with each distribution, then i + 1
gives us the number of candies to distribute at each step (1, 2, 3, 4, 5, ...).
To determine which person receives candies, we can use the modulo operator. Since we have num_people
people and we cycle through them repeatedly, i % num_people
will give us the index of the current person (0, 1, 2, ..., num_people-1, 0, 1, 2, ...).
The edge case to handle is when we don't have enough candies left. At any point, if the remaining candies are less than what the person should receive (i + 1
), we simply give them all the remaining candies and stop.
This naturally leads to a simulation approach:
- Keep an array to track total candies for each person
- Use a counter
i
to track the distribution sequence - At each step, give person at index
i % num_people
eitheri + 1
candies or whatever remains - Continue until all candies are distributed
The beauty of this approach is its simplicity - we're directly modeling the problem statement without needing complex mathematical formulas or optimizations.
Learn more about Math patterns.
Solution Approach
The solution uses a straightforward simulation approach to distribute the candies according to the rules.
Implementation Steps:
-
Initialize the result array: Create an array
ans
of sizenum_people
with all elements initialized to 0. This will store the total candies each person receives.ans = [0] * num_people
-
Set up the distribution counter: Initialize
i = 0
to track the current distribution round. This counter will continuously increment and help us determine both which person gets candies and how many they should receive. -
Main distribution loop: Continue distributing while there are candies remaining:
while candies:
-
Determine recipient and amount:
- The person at index
i % num_people
is the current recipient - They should receive
i + 1
candies in this distribution - However, if we have fewer candies remaining, they get all that's left:
min(candies, i + 1)
ans[i % num_people] += min(candies, i + 1)
- The person at index
-
Update remaining candies: Subtract the distributed amount from the total:
candies -= min(candies, i + 1)
-
Move to next distribution: Increment the counter for the next round:
i += 1
Why this works:
- The modulo operation
i % num_people
ensures we cycle through people indices 0, 1, 2, ..., num_people-1, then back to 0 - The distribution amount
i + 1
naturally follows the sequence 1, 2, 3, 4, ... as required - Using
min(candies, i + 1)
handles the edge case where we run out of candies mid-distribution - The loop automatically terminates when
candies
becomes 0
Time Complexity: O(√candies) - We distribute approximately 1 + 2 + 3 + ... + k candies, where k(k+1)/2 ≈ candies, so k ≈ √(2*candies)
Space Complexity: O(num_people) - We only use the result array to store the final distribution.
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 a detailed example with candies = 15
and num_people = 4
.
We'll track each distribution step to see how the simulation approach works:
Initial Setup:
- Result array:
ans = [0, 0, 0, 0]
(one slot per person) - Distribution counter:
i = 0
- Remaining candies:
15
Distribution Process:
Step 1: i = 0
- Person index:
0 % 4 = 0
(Person 1) - Candies to give:
min(15, 0 + 1) = 1
- Update:
ans[0] += 1
→ans = [1, 0, 0, 0]
- Remaining:
15 - 1 = 14
- Increment:
i = 1
Step 2: i = 1
- Person index:
1 % 4 = 1
(Person 2) - Candies to give:
min(14, 1 + 1) = 2
- Update:
ans[1] += 2
→ans = [1, 2, 0, 0]
- Remaining:
14 - 2 = 12
- Increment:
i = 2
Step 3: i = 2
- Person index:
2 % 4 = 2
(Person 3) - Candies to give:
min(12, 2 + 1) = 3
- Update:
ans[2] += 3
→ans = [1, 2, 3, 0]
- Remaining:
12 - 3 = 9
- Increment:
i = 3
Step 4: i = 3
- Person index:
3 % 4 = 3
(Person 4) - Candies to give:
min(9, 3 + 1) = 4
- Update:
ans[3] += 4
→ans = [1, 2, 3, 4]
- Remaining:
9 - 4 = 5
- Increment:
i = 4
Step 5: i = 4
(Back to Person 1!)
- Person index:
4 % 4 = 0
(Person 1 again) - Candies to give:
min(5, 4 + 1) = 5
- Update:
ans[0] += 5
→ans = [6, 2, 3, 4]
- Remaining:
5 - 5 = 0
- Loop ends (no candies left)
Final Result: [6, 2, 3, 4]
Key Observations:
- The modulo operation (
i % 4
) cycles us through persons 0→1→2→3→0... - Each distribution gives
i + 1
candies (1, 2, 3, 4, 5, ...) - Person 1 received candies twice (1 candy in round 1, 5 candies in round 2)
- The
min()
function would handle the case where we run out mid-distribution (though it didn't happen here)
This demonstrates how the simple simulation approach naturally handles the cyclic distribution pattern without complex logic.
Solution Implementation
1class Solution:
2 def distributeCandies(self, candies: int, num_people: int) -> List[int]:
3 # Initialize result array with zeros for each person
4 result = [0] * num_people
5
6 # Track the current distribution round (also represents candies to give)
7 distribution_round = 0
8
9 # Continue distributing while there are candies remaining
10 while candies > 0:
11 # Calculate current person's index using modulo
12 current_person = distribution_round % num_people
13
14 # Give candies: either the required amount (distribution_round + 1)
15 # or all remaining candies, whichever is smaller
16 candies_to_give = min(candies, distribution_round + 1)
17 result[current_person] += candies_to_give
18
19 # Reduce the candy count by the amount given
20 candies -= candies_to_give
21
22 # Move to next distribution round
23 distribution_round += 1
24
25 return result
26
1class Solution {
2 public int[] distributeCandies(int candies, int num_people) {
3 // Initialize result array to store candies for each person
4 int[] result = new int[num_people];
5
6 // Distribution loop: i represents the current candy amount to give (1-indexed)
7 for (int i = 0; candies > 0; i++) {
8 // Calculate which person receives candies (using modulo for circular distribution)
9 int personIndex = i % num_people;
10
11 // Calculate how many candies to give in this round
12 // Either (i + 1) candies or remaining candies, whichever is smaller
13 int candiesToGive = Math.min(candies, i + 1);
14
15 // Give candies to the current person
16 result[personIndex] += candiesToGive;
17
18 // Update remaining candies
19 candies -= candiesToGive;
20 }
21
22 return result;
23 }
24}
25
1class Solution {
2public:
3 vector<int> distributeCandies(int candies, int num_people) {
4 // Initialize result vector to store candies for each person
5 vector<int> result(num_people);
6
7 // Distribute candies in rounds
8 // i represents the current distribution number (0-indexed)
9 for (int i = 0; candies > 0; ++i) {
10 // Calculate the person index using modulo to cycle through people
11 int personIndex = i % num_people;
12
13 // Calculate candies to give: either (i+1) or remaining candies, whichever is smaller
14 int candiesToGive = min(candies, i + 1);
15
16 // Give candies to the current person
17 result[personIndex] += candiesToGive;
18
19 // Reduce the remaining candies
20 candies -= candiesToGive;
21 }
22
23 return result;
24 }
25};
26
1/**
2 * Distributes candies to people in a circular manner with increasing amounts
3 * @param candies - Total number of candies to distribute
4 * @param num_people - Number of people to distribute candies to
5 * @returns Array where each element represents candies received by each person
6 */
7function distributeCandies(candies: number, num_people: number): number[] {
8 // Initialize result array with zeros for each person
9 const result: number[] = Array(num_people).fill(0);
10
11 // Distribute candies in rounds
12 // i represents the current distribution number (0-indexed)
13 for (let distributionNumber = 0; candies > 0; distributionNumber++) {
14 // Calculate current person index using modulo for circular distribution
15 const currentPersonIndex = distributionNumber % num_people;
16
17 // Calculate candies to give: either remaining candies or (distributionNumber + 1)
18 const candiesToGive = Math.min(candies, distributionNumber + 1);
19
20 // Give candies to current person
21 result[currentPersonIndex] += candiesToGive;
22
23 // Reduce remaining candies
24 candies -= candiesToGive;
25 }
26
27 return result;
28}
29
Time and Space Complexity
The time complexity is O(max(sqrt(candies), num_people))
.
To understand why, let's analyze the loop iterations. In each iteration i
, we distribute i + 1
candies. The loop continues until all candies are distributed. The total candies distributed follows the pattern: 1 + 2 + 3 + ... + k
, where k
is the last iteration number.
Using the sum formula for arithmetic sequence: 1 + 2 + 3 + ... + k = k(k+1)/2
When this sum equals approximately candies
, we have: k(k+1)/2 ≈ candies
, which gives us k ≈ sqrt(2 * candies)
.
Therefore, the number of iterations is O(sqrt(candies))
.
However, we must also consider that the answer array has size num_people
, and in edge cases where candies
is very small, we might need to ensure at least one pass through all people. Thus, the time complexity is O(max(sqrt(candies), num_people))
.
The space complexity is O(num_people)
because we create an answer array ans
of size num_people
to store the distribution results. This is the only additional space used that scales with the input.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow in Mathematical Optimization
A common pitfall occurs when trying to optimize the solution using mathematical formulas to calculate distributions directly. Developers might attempt to find which complete rounds can be distributed using the arithmetic series sum formula, but this can lead to integer overflow issues.
Problematic Approach:
def distributeCandies(self, candies: int, num_people: int) -> List[int]:
result = [0] * num_people
# Trying to find complete rounds mathematically
# Sum = n * (n + 1) / 2 where n = complete_rounds * num_people
# This can cause overflow for large values
complete_rounds = int((-1 + (1 + 8 * candies) ** 0.5) / 2) // num_people
# ... rest of the implementation
Why it fails:
- Calculating
8 * candies
can overflow for large candy values - Square root operations on large numbers can lose precision
- Integer division might introduce rounding errors
Solution: Stick to the simple simulation approach which avoids these mathematical complexities and handles all cases correctly.
2. Off-by-One Error in Distribution Amount
Developers often confuse whether to use i
or i + 1
for the distribution amount.
Incorrect Implementation:
while candies > 0:
ans[i % num_people] += min(candies, i) # Wrong: using i instead of i+1
candies -= min(candies, i)
i += 1
Why it fails:
- First person would receive 0 candies in the first round
- The distribution pattern would be 0, 1, 2, 3... instead of 1, 2, 3, 4...
Solution: Always use i + 1
for the distribution amount since we start counting from 0 but want to give at least 1 candy.
3. Modifying the Loop Counter Inside Complex Logic
Some implementations try to handle special cases by modifying the distribution counter in multiple places.
Problematic Pattern:
i = 0 while candies > 0: if candies < i + 1: ans[i % num_people] += candies break # Might forget to increment i before breaking else: ans[i % num_people] += i + 1 candies -= i + 1 if some_condition: i += 2 # Irregular increment can break the pattern else: i += 1
Solution: Keep the increment logic simple and consistent - always increment by 1 at the end of each iteration, letting the min()
function handle edge cases naturally.
while candies:
ans[i % num_people] += min(candies, i + 1)
candies -= min(candies, i + 1)
i += 1 # Single, consistent increment
What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
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!