Facebook Pixel

3447. Assign Elements to Groups with Constraints

MediumArrayHash Table
Leetcode Link

Problem Description

You have two integer arrays: groups and elements.

Each groups[i] represents the size of the i-th group. Your task is to assign exactly one element from the elements array to each group following these rules:

  1. Assignment Rule: An element at index j can be assigned to group i only if groups[i] is divisible by elements[j]. In other words, groups[i] % elements[j] == 0.

  2. Priority Rule: If multiple elements can be assigned to a group, choose the element with the smallest index j.

  3. No Match Rule: If no element from the elements array can divide a group's size, assign -1 to that group.

  4. Reusability: The same element can be assigned to multiple groups.

Your goal is to return an array assigned where assigned[i] contains the index of the element chosen for group i, or -1 if no suitable element exists.

For example, if groups = [6, 4] and elements = [3, 2, 5]:

  • For group 0 with size 6: Both element at index 0 (value 3) and element at index 1 (value 2) can divide 6. We choose index 0 (smallest index).
  • For group 1 with size 4: Only element at index 1 (value 2) can divide 4.
  • Result: [0, 1]
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The naive approach would be to check every group against every element to find valid assignments. However, we can optimize this by thinking about the problem from a different angle.

Instead of checking each group individually, we can precompute which element should be assigned to every possible group size up to the maximum group size. This way, when we need to find the assignment for a specific group, we can simply look it up.

The key insight is that if an element elements[j] has value x, it can divide all multiples of x. So for element x, the divisible group sizes would be x, 2x, 3x, 4x, ... up to the maximum group size.

By iterating through elements in order (from smallest index to largest), we can ensure that when multiple elements can divide a group size, the one with the smallest index gets recorded first. We only update the assignment for a group size if it hasn't been assigned yet (still -1).

For example, if elements = [2, 3] and maximum group size is 6:

  • Element at index 0 (value 2) can handle group sizes: 2, 4, 6
  • Element at index 1 (value 3) can handle group sizes: 3, 6

Since we process index 0 first, group size 6 gets assigned to index 0 even though index 1 could also handle it.

This precomputation approach reduces redundant calculations, especially when there are multiple groups with the same size. We compute the assignment for each possible size once, then simply look up the result for each group.

Solution Approach

We implement the precomputation strategy using the following steps:

Step 1: Find Maximum Group Size First, we find the maximum value in the groups array, denoted as mx. This helps us determine the range of group sizes we need to consider.

mx = max(groups)

Step 2: Initialize Assignment Array We create an array d of size mx + 1 to store the element index assigned to each possible group size. Initially, all values are set to -1, indicating no assignment yet.

d = [-1] * (mx + 1)

Step 3: Process Elements and Assign to Group Sizes We iterate through the elements array with both index j and value x:

for j, x in enumerate(elements):

For each element, we first check two conditions:

  • If x > mx: The element is larger than any group size, so it can't divide any group
  • If d[x] != -1: This element value has already been assigned by an earlier element with a smaller index

If either condition is true, we skip this element:

if x > mx or d[x] != -1:
    continue

Otherwise, we iterate through all multiples of x up to mx and assign index j to those positions in array d that haven't been assigned yet:

for y in range(x, mx + 1, x):
    if d[y] == -1:
        d[y] = j

The range (x, mx + 1, x) generates the sequence x, 2x, 3x, ... up to mx.

Step 4: Build Final Result Finally, we construct the answer by looking up each group size in our precomputed array d:

return [d[x] for x in groups]

For each group size in groups, we retrieve its assigned element index from d[x]. If no element was assigned to that size, d[x] remains -1.

Time Complexity: O(n + m × (mx/min_element)) where n is the length of groups, m is the length of elements, and mx is the maximum group size.

Space Complexity: O(mx) for the assignment array d.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example to illustrate the solution approach.

Given:

  • groups = [6, 4, 8, 3]
  • elements = [3, 2, 5]

Step 1: Find Maximum Group Size

mx = max([6, 4, 8, 3]) = 8

Step 2: Initialize Assignment Array Create array d of size 9 (indices 0-8), all initialized to -1:

d = [-1, -1, -1, -1, -1, -1, -1, -1, -1]
     0    1    2    3    4    5    6    7    8

Step 3: Process Each Element

Processing element at index 0 (value 3):

  • Check: 3 ≤ 8 ✓ and d[3] == -1 ✓
  • Mark multiples of 3: positions 3, 6
d = [-1, -1, -1, 0, -1, -1, 0, -1, -1]
     0    1    2   3    4    5   6    7    8

Processing element at index 1 (value 2):

  • Check: 2 ≤ 8 ✓ and d[2] == -1 ✓
  • Mark multiples of 2: positions 2, 4, 6, 8
  • Note: position 6 already has 0, so we skip it
d = [-1, -1, 1, 0, 1, -1, 0, -1, 1]
     0    1   2   3   4    5   6    7   8

Processing element at index 2 (value 5):

  • Check: 5 ≤ 8 ✓ and d[5] == -1 ✓
  • Mark multiples of 5: position 5
d = [-1, -1, 1, 0, 1, 2, 0, -1, 1]
     0    1   2   3   4   5   6    7   8

Step 4: Build Final Result Look up each group size in array d:

  • Group size 6: d[6] = 0 (element at index 0 divides 6)
  • Group size 4: d[4] = 1 (element at index 1 divides 4)
  • Group size 8: d[8] = 1 (element at index 1 divides 8)
  • Group size 3: d[3] = 0 (element at index 0 divides 3)

Result: [0, 1, 1, 0]

This demonstrates how the precomputation ensures that each group size gets assigned the element with the smallest index that can divide it, and how the same element index can be reused for multiple groups.

Solution Implementation

1class Solution:
2    def assignElements(self, groups: List[int], elements: List[int]) -> List[int]:
3        # Find the maximum value in groups to determine the range we need to process
4        max_group_value = max(groups)
5      
6        # Initialize assignment array with -1 (indicating no assignment yet)
7        # Index represents a potential group value, value represents the assigned element index
8        assignments = [-1] * (max_group_value + 1)
9      
10        # Process each element with its index
11        for element_index, element_value in enumerate(elements):
12            # Skip if element value exceeds max group value or if already processed
13            if element_value > max_group_value or assignments[element_value] != -1:
14                continue
15          
16            # Assign current element index to all multiples of element_value
17            # This ensures each group value gets the smallest valid element index
18            for multiple in range(element_value, max_group_value + 1, element_value):
19                # Only assign if this position hasn't been assigned yet
20                if assignments[multiple] == -1:
21                    assignments[multiple] = element_index
22      
23        # Map each group value to its assigned element index
24        return [assignments[group_value] for group_value in groups]
25
1class Solution {
2    public int[] assignElements(int[] groups, int[] elements) {
3        // Find the maximum value in groups array
4        int maxGroup = Arrays.stream(groups).max().getAsInt();
5      
6        // Create an array to store the assigned element index for each possible group value
7        // Index represents the group value, value represents the assigned element index
8        int[] assignments = new int[maxGroup + 1];
9        Arrays.fill(assignments, -1);  // Initialize all assignments to -1 (unassigned)
10      
11        // Process each element to assign it to compatible groups
12        for (int elementIndex = 0; elementIndex < elements.length; ++elementIndex) {
13            int currentElement = elements[elementIndex];
14          
15            // Skip if element is larger than max group or already processed
16            if (currentElement > maxGroup || assignments[currentElement] != -1) {
17                continue;
18            }
19          
20            // Assign current element to all groups that are multiples of it
21            for (int multiple = currentElement; multiple <= maxGroup; multiple += currentElement) {
22                // Only assign if the group hasn't been assigned yet
23                if (assignments[multiple] == -1) {
24                    assignments[multiple] = elementIndex;
25                }
26            }
27        }
28      
29        // Build the result array by looking up assignments for each group
30        int groupCount = groups.length;
31        int[] result = new int[groupCount];
32        for (int i = 0; i < groupCount; ++i) {
33            result[i] = assignments[groups[i]];
34        }
35      
36        return result;
37    }
38}
39
1class Solution {
2public:
3    vector<int> assignElements(vector<int>& groups, vector<int>& elements) {
4        // Find the maximum value in groups array
5        int maxGroupValue = ranges::max(groups);
6      
7        // Initialize assignment array with -1 (indicating no assignment)
8        // Index represents group value, value represents assigned element index
9        vector<int> assignment(maxGroupValue + 1, -1);
10
11        // Iterate through each element
12        for (int elementIndex = 0; elementIndex < elements.size(); ++elementIndex) {
13            int elementValue = elements[elementIndex];
14          
15            // Skip if element value exceeds max group value or already processed
16            if (elementValue > maxGroupValue || assignment[elementValue] != -1) {
17                continue;
18            }
19          
20            // Assign current element to all groups that are multiples of elementValue
21            for (int multiple = elementValue; multiple <= maxGroupValue; multiple += elementValue) {
22                // Only assign if the group hasn't been assigned yet
23                if (assignment[multiple] == -1) {
24                    assignment[multiple] = elementIndex;
25                }
26            }
27        }
28
29        // Build result array by looking up assignments for each group
30        vector<int> result(groups.size());
31        for (int i = 0; i < groups.size(); ++i) {
32            result[i] = assignment[groups[i]];
33        }
34
35        return result;
36    }
37};
38
1/**
2 * Assigns elements to groups based on divisibility relationships.
3 * For each group value, finds the smallest index in elements array where
4 * the element divides the group value.
5 * 
6 * @param groups - Array of group values to find divisors for
7 * @param elements - Array of potential divisor elements
8 * @returns Array where each position contains the index of the first element
9 *          that divides the corresponding group value, or -1 if none exists
10 */
11function assignElements(groups: number[], elements: number[]): number[] {
12    // Find the maximum value in groups to limit our search range
13    const maxGroupValue: number = Math.max(...groups);
14  
15    // Initialize array to store the smallest index of divisor for each value
16    // Index represents the value, content represents the smallest element index that divides it
17    const divisorIndices: number[] = Array(maxGroupValue + 1).fill(-1);
18  
19    // Iterate through each element with its index
20    for (let elementIndex = 0; elementIndex < elements.length; ++elementIndex) {
21        const currentElement: number = elements[elementIndex];
22      
23        // Skip if element is larger than max group value or if we already found a divisor
24        if (currentElement > maxGroupValue || divisorIndices[currentElement] !== -1) {
25            continue;
26        }
27      
28        // Mark all multiples of currentElement up to maxGroupValue
29        // This efficiently finds all values that currentElement can divide
30        for (let multiple = currentElement; multiple <= maxGroupValue; multiple += currentElement) {
31            // Only update if no divisor has been assigned yet (keeps the smallest index)
32            if (divisorIndices[multiple] === -1) {
33                divisorIndices[multiple] = elementIndex;
34            }
35        }
36    }
37  
38    // Map each group value to its corresponding divisor index
39    return groups.map((groupValue: number) => divisorIndices[groupValue]);
40}
41

Time and Space Complexity

Time Complexity: O(M × log M + n)

The time complexity consists of two main parts:

  • Finding the maximum value in groups: O(n) where n is the length of groups
  • Iterating through elements and for each element x, iterating through its multiples up to M: For each element x in elements, we iterate through values x, 2x, 3x, ... up to M. The number of multiples of x up to M is M/x. Summing over all possible values from 1 to M, we get M/1 + M/2 + M/3 + ... + M/M = M × (1 + 1/2 + 1/3 + ... + 1/M) = M × H(M), where H(M) is the harmonic series which is approximately O(log M). Since we process at most m elements (length of elements), and in the worst case we process divisors up to M, the total is O(M × log M)
  • Building the result array: O(n)

Therefore, the total time complexity is O(M × log M + n).

Space Complexity: O(M)

The space complexity is determined by:

  • The array d of size M + 1: O(M)
  • The output array of size n: O(n) (not counted as auxiliary space since it's the required output)

Therefore, the auxiliary space complexity is O(M).

Where:

  • n is the length of the groups array
  • m is the length of the elements array
  • M is the maximum value in the groups array

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Overwriting Previously Assigned Values

The Problem: A common mistake is to unconditionally assign element indices to all multiples without checking if a better (smaller index) assignment already exists. For example:

# WRONG: This overwrites existing assignments
for multiple in range(element_value, max_group_value + 1, element_value):
    assignments[multiple] = element_index  # Overwrites without checking!

Why It Happens: When processing elements, if we don't check whether a position has already been assigned, we might replace a smaller index with a larger one, violating the priority rule.

Example Scenario:

  • groups = [6], elements = [2, 3]
  • Element at index 0 (value 2) assigns index 0 to position 6
  • Element at index 1 (value 3) would overwrite position 6 with index 1
  • Wrong result: [1] instead of correct [0]

Solution: Always check if a position is unassigned before updating it:

if assignments[multiple] == -1:
    assignments[multiple] = element_index

Pitfall 2: Not Handling the Element Value Itself

The Problem: Forgetting to check and potentially assign the element value itself (not just its multiples) can lead to incorrect results when a group size exactly matches an element value.

Why It Happens: The condition if element_value > max_group_value or assignments[element_value] != -1 might skip processing when assignments[element_value] is already set, even though we still need to process the element's other multiples.

Example Scenario:

  • groups = [4, 8], elements = [4, 2]
  • Element at index 0 (value 4) should assign index 0 to positions 4 and 8
  • If we skip entirely when assignments[4] != -1, position 8 might not get assigned

Solution: The current implementation handles this correctly by checking assignments[element_value] != -1 as an optimization - if the element value itself is already assigned by a smaller index, then ALL its multiples must also be assigned by that same smaller index (since any divisor of x is also a divisor of multiples of x).

Pitfall 3: Incorrect Range Boundaries

The Problem: Using incorrect loop boundaries when iterating through multiples:

# WRONG: Off-by-one error
for multiple in range(element_value, max_group_value, element_value):  # Missing +1

Why It Happens: Python's range() function excludes the end value, so we need max_group_value + 1 to include max_group_value itself.

Example Scenario:

  • groups = [10], elements = [5]
  • Without +1, the range would be range(5, 10, 5) producing only [5]
  • Group size 10 wouldn't get assigned even though 5 divides 10

Solution: Always use range(element_value, max_group_value + 1, element_value) to ensure the maximum group value is included in the iteration.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using input [30, 20, 10, 100, 33, 12]?

1def fun(arr: List[int]) -> List[int]:
2    import heapq
3    heapq.heapify(arr)
4    res = []
5    for i in range(3):
6        res.append(heapq.heappop(arr))
7    return res
8
1public static int[] fun(int[] arr) {
2    int[] res = new int[3];
3    PriorityQueue<Integer> heap = new PriorityQueue<>();
4    for (int i = 0; i < arr.length; i++) {
5        heap.add(arr[i]);
6    }
7    for (int i = 0; i < 3; i++) {
8        res[i] = heap.poll();
9    }
10    return res;
11}
12
1class HeapItem {
2    constructor(item, priority = item) {
3        this.item = item;
4        this.priority = priority;
5    }
6}
7
8class MinHeap {
9    constructor() {
10        this.heap = [];
11    }
12
13    push(node) {
14        // insert the new node at the end of the heap array
15        this.heap.push(node);
16        // find the correct position for the new node
17        this.bubble_up();
18    }
19
20    bubble_up() {
21        let index = this.heap.length - 1;
22
23        while (index > 0) {
24            const element = this.heap[index];
25            const parentIndex = Math.floor((index - 1) / 2);
26            const parent = this.heap[parentIndex];
27
28            if (parent.priority <= element.priority) break;
29            // if the parent is bigger than the child then swap the parent and child
30            this.heap[index] = parent;
31            this.heap[parentIndex] = element;
32            index = parentIndex;
33        }
34    }
35
36    pop() {
37        const min = this.heap[0];
38        this.heap[0] = this.heap[this.size() - 1];
39        this.heap.pop();
40        this.bubble_down();
41        return min;
42    }
43
44    bubble_down() {
45        let index = 0;
46        let min = index;
47        const n = this.heap.length;
48
49        while (index < n) {
50            const left = 2 * index + 1;
51            const right = left + 1;
52
53            if (left < n && this.heap[left].priority < this.heap[min].priority) {
54                min = left;
55            }
56            if (right < n && this.heap[right].priority < this.heap[min].priority) {
57                min = right;
58            }
59            if (min === index) break;
60            [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61            index = min;
62        }
63    }
64
65    peek() {
66        return this.heap[0];
67    }
68
69    size() {
70        return this.heap.length;
71    }
72}
73
74function fun(arr) {
75    const heap = new MinHeap();
76    for (const x of arr) {
77        heap.push(new HeapItem(x));
78    }
79    const res = [];
80    for (let i = 0; i < 3; i++) {
81        res.push(heap.pop().item);
82    }
83    return res;
84}
85

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More