Group the People Given the Group Size They Belong To: Hash Map Bucketing
Group the People Given the Group Size They Belong To (LeetCode #1282) is a medium problem that tests whether you can translate a simple observation into clean code. The trick is recognizing that this is really just a bucketing problem, and a hash map makes it trivial.
Let's break it down.
The problem
You are given an array groupSizes of length n, where groupSizes[i] is the size of the group that person i should belong to. Return a list of groups such that each person i is in a group of size groupSizes[i].
Each person belongs to exactly one group, and the problem guarantees at least one valid solution exists. You can return any valid grouping.
Example: groupSizes = [3,3,3,3,3,1,3]
Output: [[5], [0,1,2], [3,4,6]]
Person 5 needs a group of size 1, so they go in a group alone. The remaining six people all need groups of size 3, so they split into two groups of three.
The brute force approach
You could try every possible partition of people into groups and check which partition satisfies all the constraints. That is combinatorial and absurdly slow. You could also try sorting people by their group size and then slicing, which works but introduces unnecessary complexity. There is a much cleaner path.
The key insight
Think about it this way: every person tells you exactly what size group they need. If you collect all people who need the same size into a temporary bucket, you can simply flush that bucket into the result every time it reaches the target size.
The algorithm:
- Create a hash map where the key is a group size and the value is a list of people waiting for that size
- Iterate through each person. Add them to the bucket for their group size
- Whenever a bucket reaches its target size, move that bucket into the result and start a fresh one
- Return the result
One pass. One hash map. O(n) time.
The code
Here is the clean Python solution:
from collections import defaultdict
def groupThePeople(groupSizes: list[int]) -> list[list[int]]:
buckets = defaultdict(list)
result = []
for person, size in enumerate(groupSizes):
buckets[size].append(person)
if len(buckets[size]) == size:
result.append(buckets[size])
buckets[size] = []
return result
Let's walk through what each line does:
buckets = defaultdict(list)creates a hash map where each key auto-initializes to an empty list. The key is a group size, the value is the list of people currently waiting for that size.for person, size in enumerate(groupSizes)gives us both the index (the person ID) and the value (the group size they need).buckets[size].append(person)adds the current person to the bucket for their required group size.if len(buckets[size]) == sizechecks whether the bucket is full. A bucket for sizekis full when it contains exactlykpeople.result.append(buckets[size])moves the full bucket into the result list.buckets[size] = []resets the bucket so future people with the same size start filling a new group.
We reassign buckets[size] = [] instead of calling .clear() because result already holds a reference to the old list. If we cleared it in place, we would wipe out the group we just added to the result.
Visual walkthrough
Let's trace through groupSizes = [3,3,3,3,3,1,3] step by step. Watch how the hash map fills up and flushes groups as each person is processed.
Step 1: Process person 0 (groupSize = 3)
| Size key | Bucket | Status |
|---|---|---|
| 3 | [0] | 1/3 |
Person 0 needs a group of size 3. Add them to the bucket for size 3. Bucket has 1/3 people.
Step 2: Process person 1 (groupSize = 3)
| Size key | Bucket | Status |
|---|---|---|
| 3 | [0, 1] | 2/3 |
Person 1 also needs size 3. Add to the same bucket. Now 2/3 people.
Step 3: Process person 2 (groupSize = 3) - Bucket full!
| Size key | Bucket | Status |
|---|---|---|
| 3 | [0, 1, 2] | Flushed to result |
result = [[0, 1, 2]]
Person 2 fills the size-3 bucket to 3/3. Flush [0, 1, 2] into the result and clear the bucket.
Step 4: Process person 3 (groupSize = 3)
| Size key | Bucket | Status |
|---|---|---|
| 3 | [3] | 1/3 |
Fresh bucket for size 3 (the old one was flushed). Person 3 starts a new group. 1/3 people.
Step 5: Process person 4 (groupSize = 3)
| Size key | Bucket | Status |
|---|---|---|
| 3 | [3, 4] | 2/3 |
Person 4 joins the size-3 bucket. Now 2/3 people.
Step 6: Process person 5 (groupSize = 1) - Bucket full!
| Size key | Bucket | Status |
|---|---|---|
| 3 | [3, 4] | 2/3 |
| 1 | [5] | Flushed to result |
result = [[0, 1, 2], [5]]
Person 5 needs a group of size 1. The bucket fills instantly (1/1). Flush [5] into the result.
Step 7: Process person 6 (groupSize = 3) - Bucket full!
| Size key | Bucket | Status |
|---|---|---|
| 3 | [3, 4, 6] | Flushed to result |
result = [[0, 1, 2], [5], [3, 4, 6]]
Person 6 fills the size-3 bucket to 3/3. Flush [3, 4, 6] into the result. All people assigned.
After processing all seven people, the result contains three groups: [[0,1,2], [5], [3,4,6]]. Every person is in exactly one group, and every group has the correct size.
Complexity analysis
| Time | Space | |
|---|---|---|
| Bucketing approach | O(n) | O(n) |
Time: O(n). We iterate through the array once. Each person triggers a constant-time append and a constant-time length check. Flushing a bucket is O(1) amortized because each person is flushed exactly once across the entire run.
Space: O(n). The hash map and the result list together hold every person exactly once. The total number of entries across all buckets and result groups is always n.
This is optimal. You have to look at every person at least once, so you cannot do better than O(n).
The building blocks
This problem is built from two core patterns that show up across many hash map problems.
Bucket-and-flush
The act of collecting items into a temporary container keyed by some property, then flushing (emitting) the container when it meets a condition. The template looks like this:
from collections import defaultdict
buckets = defaultdict(list)
result = []
for item in collection:
key = classify(item)
buckets[key].append(item)
if should_flush(buckets[key], key):
result.append(buckets[key])
buckets[key] = []
The only things that change between problems are the classify function and the should_flush condition:
- Group People by Size:
classifyreturns the group size,should_flushtriggers when the bucket length equals the key - Group Anagrams:
classifyreturns the sorted characters, flush happens after all items are processed (no mid-stream flush) - Batch processing:
classifymight return a category,should_flushtriggers at a fixed batch size
Enumerate for index + value
Whenever you need both the index and the value from an array, enumerate is the idiomatic Python tool. It avoids manual index tracking and makes the intent clearer. You will reach for this in almost every array problem.
Edge cases to watch for
A few things to keep in mind:
- Everyone in one group. If
groupSizes = [3,3,3], there is one bucket for size 3 and it flushes once. The result is[[0,1,2]]. Works without any special handling. - Everyone alone. If
groupSizes = [1,1,1], each person immediately fills and flushes a size-1 bucket. The result is[[0], [1], [2]]. - Large uniform groups. If all n people have the same group size k, you get n/k groups of size k. The bucket fills and flushes n/k times. No issues.
- Mixed sizes. The hash map naturally separates people by size. People with size 2 never interfere with people who need size 5. Each bucket operates independently.
From understanding to recall
This problem feels almost too simple once you see the solution. "Just bucket people by size and flush when full." But in practice, the bucket-and-flush pattern is easy to forget because you rarely think of it as a named pattern. You think of it as "obvious."
The danger is that when you encounter a harder variant, like batching items with constraints, or grouping with a more complex flush condition, you do not immediately recognize the same skeleton. You end up reinventing the logic from scratch instead of reaching for the template.
Spaced repetition fixes this. You practice writing the bucket-and-flush loop from memory at increasing intervals. After a few rounds, the pattern becomes automatic. When a new problem says "group these items and do something when a group is complete," your fingers are already typing defaultdict(list) before you have finished reading the constraints.
Related posts
- Group Anagrams - the classic group-by-key hash map problem, same defaultdict template with a different key function
- Top K Frequent Elements - counting frequencies into a hash map, another core hash map technique
- Hash Maps: the Essential Data Structure - deep dive into how hash maps work and why they make bucketing O(1)