Skip to content
← All posts

Group the People Given the Group Size They Belong To: Hash Map Bucketing

5 min read
leetcodeproblemmediumarrayshash-map

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]]

groupSizes3i=03i=13i=23i=33i=41i=53i=6size 15size 3012size 3346
groupSizes = [3,3,3,3,3,1,3]. People with group size 1 form a single group [5]. People with group size 3 fill two groups: [0,1,2] and [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:

  1. Create a hash map where the key is a group size and the value is a list of people waiting for that size
  2. Iterate through each person. Add them to the bucket for their group size
  3. Whenever a bucket reaches its target size, move that bucket into the result and start a fresh one
  4. 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]) == size checks whether the bucket is full. A bucket for size k is full when it contains exactly k people.
  • 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)

30313233341536
Size keyBucketStatus
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)

30313233341536
Size keyBucketStatus
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!

30313233341536
Size keyBucketStatus
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)

30313233341536
Size keyBucketStatus
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)

30313233341536
Size keyBucketStatus
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!

30313233341536
Size keyBucketStatus
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!

30313233341536
Size keyBucketStatus
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

TimeSpace
Bucketing approachO(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: classify returns the group size, should_flush triggers when the bucket length equals the key
  • Group Anagrams: classify returns the sorted characters, flush happens after all items are processed (no mid-stream flush)
  • Batch processing: classify might return a category, should_flush triggers 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