1282. Group the People Given the Group Size They Belong To
Problem Description
You have n
people numbered from 0
to n - 1
, and you need to organize them into groups. Each person i
has a required group size specified in groupSizes[i]
, which tells you the size of the group that person i
must be placed in.
For example, if groupSizes[1] = 3
, then person 1
must be placed in a group that contains exactly 3
people total.
Your task is to return a list of groups where:
- Each person
i
is placed in a group of sizegroupSizes[i]
- Every person appears in exactly one group
- All people from
0
ton - 1
are assigned to a group
The solution uses a hash table approach where we first collect all people by their required group size. For instance, if multiple people need to be in groups of size 3
, we collect all of them together. Then, we partition these collected people into actual groups of the required size.
The key insight is that if we have 6
people who all need to be in groups of size 3
, we can form 2
groups of size 3
. The code accomplishes this by:
- Grouping people by their required group size using a dictionary
- Splitting each collection into chunks of the appropriate size
Since multiple valid solutions may exist (the exact composition of groups can vary as long as size requirements are met), any valid grouping is acceptable.
Intuition
The key observation is that people with the same group size requirement can be grouped together. If multiple people need to be in groups of size k
, we can collect them and then divide them into groups of exactly k
people each.
Think of it like organizing a party where guests have specific table size preferences. If 6 people want to sit at tables of 3, we know we need exactly 2 tables of size 3. We don't need to worry about which specific people sit together - we just need to ensure each table has exactly 3 people.
This leads us to a two-step approach:
-
Collect by requirement: First, gather all people who have the same group size requirement. If persons
0
,2
, and5
all need groups of size3
, we collect them together. -
Partition into groups: Once we have all people with the same requirement collected, we can split them into actual groups. If we collected 9 people who all need groups of size
3
, we simply create 3 groups of 3 people each.
The beauty of this approach is that the problem guarantees a valid solution exists. This means if we have people requiring groups of size k
, the total count of such people will always be divisible by k
. For example, we'll never have 5 people wanting groups of size 3 - it will always be 3, 6, 9, etc.
By using a hash table (or array) indexed by group size, we can efficiently collect people, and then use list slicing to partition them into appropriately sized groups. The expression v[j : j + i]
creates chunks of size i
from the list v
, where i
is the required group size and v
contains all people needing that group size.
Learn more about Greedy patterns.
Solution Approach
The solution uses a hash table (dictionary) to organize people by their required group sizes, then partitions them into actual groups.
Step 1: Group Collection Using Hash Table
We create a defaultdict(list)
called g
where:
- The key is the group size requirement
- The value is a list of people (their IDs) who need that group size
g = defaultdict(list)
for i, v in enumerate(groupSizes):
g[v].append(i)
For example, if groupSizes = [3, 3, 3, 3, 3, 1, 3]
:
g[3]
will contain[0, 1, 2, 3, 4, 6]
(6 people needing groups of size 3)g[1]
will contain[5]
(1 person needing a group of size 1)
Step 2: Partition into Groups
Once we have all people organized by their required group size, we partition each list into chunks of the appropriate size:
return [v[j : j + i] for i, v in g.items() for j in range(0, len(v), i)]
This list comprehension does the following:
for i, v in g.items()
: Iterate through each group sizei
and its list of peoplev
for j in range(0, len(v), i)
: Create starting indicesj
at intervals ofi
(0, i, 2i, 3i, ...)v[j : j + i]
: Extract a slice of exactlyi
people starting from indexj
Using our example:
- For
g[3] = [0, 1, 2, 3, 4, 6]
with size 3:j = 0
: Extract[0, 1, 2]
(positions 0 to 2)j = 3
: Extract[3, 4, 6]
(positions 3 to 5)
- For
g[1] = [5]
with size 1:j = 0
: Extract[5]
(position 0 to 0)
Alternative: Array-Based Approach
As mentioned in the reference, since n
has a small range, we could use an array instead of a hash table:
g = [[] for _ in range(n + 1)]
This would be slightly more efficient as array access is faster than hash table operations, but the logic remains the same.
The time complexity is O(n)
where we iterate through all people once to group them and once more to partition them. The space complexity is also O(n)
for storing the intermediate groupings.
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 concrete example with groupSizes = [2, 1, 3, 3, 3, 2]
.
We have 6 people (indexed 0 to 5) with the following requirements:
- Person 0 needs a group of size 2
- Person 1 needs a group of size 1
- Person 2 needs a group of size 3
- Person 3 needs a group of size 3
- Person 4 needs a group of size 3
- Person 5 needs a group of size 2
Step 1: Collect people by their required group size
We create a hash table to group people by their size requirement:
- When we see person 0 needs size 2, we add 0 to
g[2]
- When we see person 1 needs size 1, we add 1 to
g[1]
- When we see person 2 needs size 3, we add 2 to
g[3]
- When we see person 3 needs size 3, we add 3 to
g[3]
- When we see person 4 needs size 3, we add 4 to
g[3]
- When we see person 5 needs size 2, we add 5 to
g[2]
After this step, our hash table looks like:
g[1] = [1] g[2] = [0, 5] g[3] = [2, 3, 4]
Step 2: Partition each collection into groups of the required size
Now we split each list into chunks of the appropriate size:
-
For
g[1] = [1]
with required size 1:- We take slices of size 1:
[1]
- This forms one group:
[1]
- We take slices of size 1:
-
For
g[2] = [0, 5]
with required size 2:- We take slices of size 2:
[0, 5]
- This forms one group:
[0, 5]
- We take slices of size 2:
-
For
g[3] = [2, 3, 4]
with required size 3:- We take slices of size 3:
[2, 3, 4]
- This forms one group:
[2, 3, 4]
- We take slices of size 3:
Final Result: [[1], [0, 5], [2, 3, 4]]
Each person is placed in exactly one group, and each group has the size that all its members required. Person 0 and 5 both wanted groups of size 2 and ended up together in a group of 2. Persons 2, 3, and 4 all wanted groups of size 3 and formed a group of 3. Person 1 wanted a group of size 1 and got their own group.
Solution Implementation
1from collections import defaultdict
2from typing import List
3
4class Solution:
5 def groupThePeople(self, groupSizes: List[int]) -> List[List[int]]:
6 # Dictionary to store people grouped by their required group size
7 # Key: group size, Value: list of person indices
8 groups_by_size = defaultdict(list)
9
10 # Iterate through each person and their required group size
11 for person_index, required_group_size in enumerate(groupSizes):
12 # Add person's index to the list of people requiring the same group size
13 groups_by_size[required_group_size].append(person_index)
14
15 # Build the final result by splitting people into groups of required size
16 result = []
17 for group_size, people_indices in groups_by_size.items():
18 # Split the list of people into chunks of size 'group_size'
19 for start_index in range(0, len(people_indices), group_size):
20 # Extract a group of exactly 'group_size' people
21 group = people_indices[start_index:start_index + group_size]
22 result.append(group)
23
24 return result
25
1class Solution {
2 public List<List<Integer>> groupThePeople(int[] groupSizes) {
3 int n = groupSizes.length;
4
5 // Create an array of lists where index represents group size
6 // and value is a list of person indices who belong to that group size
7 List<Integer>[] groupsBySize = new List[n + 1];
8 Arrays.setAll(groupsBySize, index -> new ArrayList<>());
9
10 // Populate the array: add each person's index to their corresponding group size
11 for (int personIndex = 0; personIndex < n; personIndex++) {
12 int size = groupSizes[personIndex];
13 groupsBySize[size].add(personIndex);
14 }
15
16 // Result list to store all groups
17 List<List<Integer>> result = new ArrayList<>();
18
19 // Process each group size
20 for (int groupSize = 0; groupSize < groupsBySize.length; groupSize++) {
21 List<Integer> peopleWithThisGroupSize = groupsBySize[groupSize];
22
23 // Split people with same group size into actual groups
24 // Each group will have exactly 'groupSize' number of people
25 for (int startIndex = 0; startIndex < peopleWithThisGroupSize.size(); startIndex += groupSize) {
26 // Add a sublist of size 'groupSize' to the result
27 result.add(peopleWithThisGroupSize.subList(startIndex, startIndex + groupSize));
28 }
29 }
30
31 return result;
32 }
33}
34
1class Solution {
2public:
3 vector<vector<int>> groupThePeople(vector<int>& groupSizes) {
4 int n = groupSizes.size();
5
6 // Create buckets where index represents group size
7 // Each bucket stores indices of people who belong to groups of that size
8 vector<vector<int>> buckets(n + 1);
9
10 // Populate buckets: person at index i belongs to a group of size groupSizes[i]
11 for (int i = 0; i < n; ++i) {
12 buckets[groupSizes[i]].push_back(i);
13 }
14
15 vector<vector<int>> result;
16
17 // Process each bucket
18 for (int groupSize = 0; groupSize < buckets.size(); ++groupSize) {
19 // Skip empty buckets (groupSize 0 or no people for this group size)
20 if (buckets[groupSize].empty()) {
21 continue;
22 }
23
24 // Split people in this bucket into groups of size 'groupSize'
25 for (int startIdx = 0; startIdx < buckets[groupSize].size(); startIdx += groupSize) {
26 // Create a group by taking 'groupSize' consecutive people from the bucket
27 vector<int> currentGroup(buckets[groupSize].begin() + startIdx,
28 buckets[groupSize].begin() + startIdx + groupSize);
29 result.push_back(currentGroup);
30 }
31 }
32
33 return result;
34 }
35};
36
1/**
2 * Groups people into groups based on their required group sizes
3 * @param groupSizes - Array where groupSizes[i] is the required group size for person i
4 * @returns Array of groups, where each group contains indices of people in that group
5 */
6function groupThePeople(groupSizes: number[]): number[][] {
7 const totalPeople: number = groupSizes.length;
8
9 // Create buckets to store people indices by their required group size
10 // Index represents group size, value is array of person indices
11 const peopleBySizeRequirement: number[][] = Array.from(
12 { length: totalPeople + 1 },
13 () => []
14 );
15
16 // Distribute people into buckets based on their required group size
17 for (let personIndex = 0; personIndex < groupSizes.length; personIndex++) {
18 const requiredSize: number = groupSizes[personIndex];
19 peopleBySizeRequirement[requiredSize].push(personIndex);
20 }
21
22 const resultGroups: number[][] = [];
23
24 // Process each bucket and form groups of the corresponding size
25 for (let groupSize = 1; groupSize <= totalPeople; groupSize++) {
26 const peopleRequiringThisSize: number[] = peopleBySizeRequirement[groupSize];
27
28 // Split people requiring same group size into actual groups
29 for (let startIndex = 0; startIndex < peopleRequiringThisSize.length; startIndex += groupSize) {
30 // Extract a group of exactly 'groupSize' people
31 const currentGroup: number[] = peopleRequiringThisSize.slice(
32 startIndex,
33 startIndex + groupSize
34 );
35 resultGroups.push(currentGroup);
36 }
37 }
38
39 return resultGroups;
40}
41
Time and Space Complexity
Time Complexity: O(n)
The algorithm consists of two main parts:
- Building the dictionary
g
by iterating throughgroupSizes
once withenumerate()
, which takesO(n)
time where each append operation isO(1)
. - Constructing the result list using a list comprehension that iterates through all dictionary items and slices the lists. Since each person (index) appears exactly once in the final result, the total number of elements processed across all slices is
n
, making this part alsoO(n)
.
Therefore, the overall time complexity is O(n) + O(n) = O(n)
.
Space Complexity: O(n)
The space usage includes:
- The dictionary
g
which stores alln
indices from the input array, usingO(n)
space. - The output list which must contain all
n
people distributed across various groups, also usingO(n)
space.
Since the output space is typically not counted against space complexity (as it's required for the answer), the auxiliary space complexity is O(n)
for the dictionary. If we count the output space, the total space complexity remains O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Assuming Invalid Input or Inconsistent Group Sizes
The Problem: A common mistake is overthinking the problem and trying to handle cases where the total number of people requiring a specific group size isn't divisible by that group size. For instance, worrying about what to do if 5 people all require groups of size 3.
Why This Happens: Developers often add unnecessary validation or error handling code like:
if len(people_indices) % group_size != 0:
# Handle error or raise exception
raise ValueError("Cannot form complete groups")
The Solution: The problem guarantees that a valid solution always exists. If people require groups of a certain size, the input will always provide exactly the right number of people to form complete groups. Trust the problem constraints and avoid adding unnecessary complexity.
Pitfall 2: Modifying the Dictionary While Building Groups
The Problem: Some developers try to optimize by removing people from the dictionary as they form groups:
groups_by_size = defaultdict(list)
for i, v in enumerate(groupSizes):
groups_by_size[v].append(i)
# Trying to form groups immediately
if len(groups_by_size[v]) == v:
result.append(groups_by_size[v])
groups_by_size[v] = [] # Clear the list
Why This is Problematic: While this approach can work, it's more error-prone and harder to debug. It mixes the collection phase with the grouping phase, making the logic less clear.
The Solution: Keep the two phases separate - first collect all people by their required group size, then partition them into groups. This makes the code cleaner and easier to understand:
# Phase 1: Collect
groups_by_size = defaultdict(list)
for i, v in enumerate(groupSizes):
groups_by_size[v].append(i)
# Phase 2: Partition
result = []
for group_size, people in groups_by_size.items():
for j in range(0, len(people), group_size):
result.append(people[j:j + group_size])
Pitfall 3: Using Wrong Variable in Slicing
The Problem: When using nested list comprehension or loops, it's easy to mix up variables:
# Wrong: using 'v' (the list) instead of 'i' (the group size) for step
return [v[j : j + i] for i, v in g.items() for j in range(0, len(v), v)]
# ^ Wrong!
Why This Happens:
With variables like i
, v
, and j
, it's easy to confuse which represents what, especially in compact list comprehensions.
The Solution: Use descriptive variable names to avoid confusion:
result = []
for group_size, people_list in groups_by_size.items():
for start_idx in range(0, len(people_list), group_size):
result.append(people_list[start_idx:start_idx + group_size])
How does quick sort divide the problem into subproblems?
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
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!