Facebook Pixel

3659. Partition Array Into K-Distinct Groups

Problem Description

You are given an integer array nums and an integer k.

Your task is to determine whether you can partition all elements of nums into groups where:

  • Each group contains exactly k distinct elements
  • Every element in nums must be assigned to exactly one group

Return true if such a partition is possible, otherwise return false.

For example, if nums = [1, 2, 3, 3, 4, 4] and k = 3, you could form two groups: [1, 2, 3] and [3, 4, 4]. Each group has exactly 3 distinct elements, and all elements from the original array are used exactly once.

The key insight is that if we have n total elements and want groups of size k, we need n to be divisible by k to form m = n/k groups. Additionally, since each element can appear in multiple groups but each group needs distinct elements, no element can appear more than m times in the array. If any element appears more than m times, we won't be able to distribute all its occurrences across the m groups while maintaining the distinct element requirement within each group.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Let's think about what constraints we have when trying to partition the array into groups of k distinct elements.

First, if we have n total elements and each group must have exactly k elements, then we need n to be perfectly divisible by k. If not, we'll have leftover elements that can't form a complete group. This gives us our first condition: n % k == 0.

Now, assuming n is divisible by k, we'll have exactly m = n / k groups to form.

Here's the crucial observation: since each group must contain distinct elements, an element that appears multiple times in the array must be distributed across different groups. For instance, if element 5 appears 3 times in the array, these three occurrences must go into 3 different groups.

This leads to our key constraint: if any element appears more than m times, it's impossible to satisfy the requirements. Why? Because we only have m groups total, and each occurrence of that element must go into a different group (to maintain the "distinct elements per group" rule). If an element appears more than m times, we simply don't have enough groups to place all its occurrences.

Conversely, if every element appears at most m times, we can always find a valid partition. We can think of it as a matching problem where we're assigning element occurrences to groups, and as long as no element exceeds the number of available groups, a valid assignment exists.

Therefore, the solution reduces to two simple checks:

  1. Is n divisible by k?
  2. Does any element appear more than m = n/k times?

If both conditions are satisfied, we can partition the array as required.

Solution Approach

The implementation follows directly from our intuition and consists of three main steps:

  1. Check divisibility: First, we calculate the quotient m and remainder mod when dividing the length of the array n by k:

    m, mod = divmod(len(nums), k)

    If mod is not zero (meaning n is not divisible by k), we immediately return False since we cannot form complete groups.

  2. Count element frequencies: We use Python's Counter from the collections module to count the occurrence of each element in the array:

    Counter(nums)

    This gives us a dictionary where keys are the unique elements and values are their frequencies.

  3. Check maximum frequency: We find the maximum frequency among all elements using:

    max(Counter(nums).values())

    If this maximum frequency exceeds m (the number of groups we can form), we return False. Otherwise, we return True.

The complete solution elegantly combines these checks:

def partitionArray(self, nums: List[int], k: int) -> bool:
    m, mod = divmod(len(nums), k)
    if mod:
        return False
    return max(Counter(nums).values()) <= m

Time Complexity: O(n) where n is the length of the array, as we need to count the frequency of each element.

Space Complexity: O(d) where d is the number of distinct elements in the array, needed to store the frequency count.

The beauty of this solution lies in its simplicity - instead of trying to construct the actual groups, we identify the necessary and sufficient conditions for a valid partition to exist and check only those conditions.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through the solution with nums = [1, 2, 3, 3, 4, 4, 4] and k = 3.

Step 1: Check if we can form complete groups

  • Array length n = 7
  • Calculate m = n // k = 7 // 3 = 2 (number of groups)
  • Calculate mod = n % k = 7 % 3 = 1 (remainder)
  • Since mod = 1 ≠ 0, we cannot divide 7 elements into groups of exactly 3

At this point, we return False immediately. We need 1 more element to form complete groups.

Let's modify the example to nums = [1, 2, 3, 3, 4, 4] and k = 3 for a complete walkthrough:

Step 1: Check divisibility

  • Array length n = 6
  • Calculate m = n // k = 6 // 3 = 2 (we can form 2 groups)
  • Calculate mod = n % k = 6 % 3 = 0 (no remainder)
  • Since mod = 0, we can potentially form complete groups ✓

Step 2: Count element frequencies

  • Element frequencies: {1: 1, 2: 1, 3: 2, 4: 2}
  • Maximum frequency = 2

Step 3: Check if max frequency ≤ number of groups

  • Maximum frequency (2) ≤ number of groups (2)? Yes ✓
  • Return True

This works because:

  • We can form Group 1: [1, 2, 3] (using one occurrence of 3)
  • We can form Group 2: [3, 4, 4] (using the second occurrence of 3 and both 4s)

Now let's see a failing case with nums = [1, 1, 1, 2, 3, 4] and k = 2:

Step 1: Check divisibility

  • Array length n = 6
  • Calculate m = n // k = 6 // 2 = 3 (we can form 3 groups)
  • Calculate mod = n % k = 6 % 2 = 0

Step 2: Count element frequencies

  • Element frequencies: {1: 3, 2: 1, 3: 1, 4: 1}
  • Maximum frequency = 3

Step 3: Check if max frequency ≤ number of groups

  • Maximum frequency (3) ≤ number of groups (3)? Yes ✓
  • Return True

This works: Group 1: [1, 2], Group 2: [1, 3], Group 3: [1, 4]

Solution Implementation

1from typing import List
2from collections import Counter
3
4
5class Solution:
6    def partitionArray(self, nums: List[int], k: int) -> bool:
7        """
8        Determines if the array can be partitioned into groups of size k,
9        where each group contains identical elements.
10      
11        Args:
12            nums: List of integers to partition
13            k: Size of each partition group
14          
15        Returns:
16            True if array can be partitioned into equal groups of size k,
17            False otherwise
18        """
19        # Calculate number of complete groups and remainder
20        num_groups, remainder = divmod(len(nums), k)
21      
22        # If array length is not divisible by k, partitioning is impossible
23        if remainder:
24            return False
25      
26        # Count frequency of each element in the array
27        element_frequency = Counter(nums)
28      
29        # Check if any element appears more than num_groups times
30        # If so, it's impossible to distribute that element across groups
31        max_frequency = max(element_frequency.values())
32      
33        return max_frequency <= num_groups
34
1class Solution {
2    public boolean partitionArray(int[] nums, int k) {
3        // Get the total number of elements
4        int arrayLength = nums.length;
5      
6        // Check if array can be evenly divided into groups of size k
7        if (arrayLength % k != 0) {
8            return false;
9        }
10      
11        // Calculate the number of groups (maximum occurrences allowed per element)
12        int maxGroupCount = arrayLength / k;
13      
14        // Find the maximum value in the array to determine frequency array size
15        int maxValue = Arrays.stream(nums).max().getAsInt();
16      
17        // Create frequency array to count occurrences of each element
18        int[] frequencyCount = new int[maxValue + 1];
19      
20        // Count frequency of each element and check if any exceeds the limit
21        for (int currentNum : nums) {
22            frequencyCount[currentNum]++;
23          
24            // If any element appears more than maxGroupCount times,
25            // it's impossible to partition into valid groups
26            if (frequencyCount[currentNum] > maxGroupCount) {
27                return false;
28            }
29        }
30      
31        // All elements have valid frequencies for partitioning
32        return true;
33    }
34}
35
1class Solution {
2public:
3    bool partitionArray(vector<int>& nums, int k) {
4        int n = nums.size();
5      
6        // Check if array size is divisible by k
7        if (n % k != 0) {
8            return false;
9        }
10      
11        // Calculate maximum allowed frequency for each element
12        int maxFrequency = n / k;
13      
14        // Find the maximum element in the array
15        int maxElement = *max_element(nums.begin(), nums.end());
16      
17        // Create frequency array to count occurrences of each element
18        vector<int> frequency(maxElement + 1, 0);
19      
20        // Count frequency of each element and check if any exceeds the limit
21        for (int num : nums) {
22            frequency[num]++;
23            if (frequency[num] > maxFrequency) {
24                return false;
25            }
26        }
27      
28        return true;
29    }
30};
31
1/**
2 * Determines if an array can be partitioned into groups of size k
3 * where each group contains identical elements
4 * @param nums - The input array of numbers to partition
5 * @param k - The size of each partition group
6 * @returns true if the array can be partitioned, false otherwise
7 */
8function partitionArray(nums: number[], k: number): boolean {
9    // Get the length of the input array
10    const arrayLength: number = nums.length;
11  
12    // Check if array length is divisible by k
13    // If not, partitioning into equal groups is impossible
14    if (arrayLength % k !== 0) {
15        return false;
16    }
17  
18    // Calculate maximum allowed frequency for any element
19    // Each element can appear at most arrayLength/k times
20    const maxFrequency: number = arrayLength / k;
21  
22    // Find the maximum value in the array to determine frequency array size
23    const maxValue: number = Math.max(...nums);
24  
25    // Create frequency array to count occurrences of each element
26    // Index represents the element value, value represents its count
27    const frequencyCount: number[] = Array(maxValue + 1).fill(0);
28  
29    // Count frequency of each element
30    for (const element of nums) {
31        // Increment count and check if it exceeds maximum allowed frequency
32        frequencyCount[element]++;
33        if (frequencyCount[element] > maxFrequency) {
34            // If any element appears more than maxFrequency times,
35            // it's impossible to partition properly
36            return false;
37        }
38    }
39  
40    // All elements have valid frequencies, partitioning is possible
41    return true;
42}
43

Time and Space Complexity

The time complexity is O(n), where n is the length of the array nums. This is because:

  • Computing len(nums) takes O(1) time
  • divmod(len(nums), k) takes O(1) time
  • Counter(nums) iterates through all n elements once to count their frequencies, which takes O(n) time
  • Counter(nums).values() returns a view of the values in O(1) time
  • max(Counter(nums).values()) iterates through all unique values in the counter to find the maximum, which in the worst case is O(n) when all elements are unique

The space complexity is O(n) or O(M), where n is the length of the array and M is the number of unique elements in the array. This is because:

  • Counter(nums) creates a dictionary that stores each unique element as a key with its frequency as the value
  • In the worst case when all elements are unique, the counter stores n key-value pairs, resulting in O(n) space
  • In the best case when all elements are the same, the counter stores only 1 key-value pair, resulting in O(1) space
  • Generally, the space complexity is O(M) where M is the number of distinct elements in nums, and M ≤ n

Common Pitfalls

Pitfall 1: Misunderstanding the Problem Requirements

The Issue: The most critical pitfall is misinterpreting what "k distinct elements" means. Many developers initially think the problem asks for groups where each group contains k identical elements (like [1,1,1] when k=3), when it actually requires k DIFFERENT elements in each group (like [1,2,3] when k=3).

Why It Happens: The example [3, 4, 4] in the problem description can be misleading - it shows a group with duplicate values, but the key is that it has 3 distinct elements (3, 4, and 4 are three positions with two distinct values).

The Fix: Re-read the problem carefully. The constraint is that each group must have exactly k elements where all elements within a single group are distinct from each other. This means:

  • If k=3, valid groups look like [1,2,3], [4,5,6], not [1,1,1]
  • No element can repeat within the same group

Pitfall 2: Incorrect Frequency Check Logic

The Issue: The current solution checks if max(element_frequency.values()) <= num_groups, but this is actually checking the wrong constraint. This would be correct if we needed groups of identical elements, but for groups of distinct elements, the logic is different.

The Correct Approach: For groups of k distinct elements:

def partitionArray(self, nums: List[int], k: int) -> bool:
    n = len(nums)
  
    # Array length must be divisible by k
    if n % k != 0:
        return False
  
    num_groups = n // k
    element_frequency = Counter(nums)
  
    # We need exactly k distinct elements per group
    # So we need at least k distinct elements total
    if len(element_frequency) < k:
        return False
  
    # Each element can appear at most num_groups times
    # (once per group maximum since elements must be distinct within groups)
    for freq in element_frequency.values():
        if freq > num_groups:
            return False
  
    return True

Pitfall 3: Edge Case Handling

Missing Edge Cases:

  1. Empty array: nums = [] with any k
  2. k larger than array length: nums = [1,2] with k = 3
  3. k equals 1: Every element forms its own group
  4. All elements are identical: nums = [1,1,1,1] with k = 2

Enhanced Solution with Edge Cases:

def partitionArray(self, nums: List[int], k: int) -> bool:
    # Handle empty array
    if not nums:
        return True
  
    n = len(nums)
  
    # Handle k = 1 (each element is its own group)
    if k == 1:
        return True
  
    # Basic divisibility check
    if n % k != 0:
        return False
  
    num_groups = n // k
    element_frequency = Counter(nums)
  
    # Need at least k distinct elements to form even one valid group
    if len(element_frequency) < k:
        return False
  
    # Check frequency constraint
    return all(freq <= num_groups for freq in element_frequency.values())

Pitfall 4: Performance Optimization Oversight

The Issue: Using max(Counter(nums).values()) creates the Counter twice if you also need to check the number of distinct elements, which is inefficient.

Better Approach:

def partitionArray(self, nums: List[int], k: int) -> bool:
    if not nums or k == 1:
        return True
      
    n = len(nums)
    if n % k != 0:
        return False
  
    num_groups = n // k
    element_frequency = Counter(nums)  # Create once, use multiple times
  
    # Check both constraints using the same Counter
    if len(element_frequency) < k:
        return False
  
    return max(element_frequency.values()) <= num_groups
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using the following tree as input?

1def serialize(root):
2    res = []
3    def dfs(root):
4        if not root:
5            res.append('x')
6            return
7        res.append(root.val)
8        dfs(root.left)
9        dfs(root.right)
10    dfs(root)
11    return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4    StringJoiner res = new StringJoiner(" ");
5    serializeDFS(root, res);
6    return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10    if (root == null) {
11        result.add("x");
12        return;
13    }
14    result.add(Integer.toString(root.val));
15    serializeDFS(root.left, result);
16    serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2    let res = [];
3    serialize_dfs(root, res);
4    return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8    if (!root) {
9        res.push("x");
10        return;
11    }
12    res.push(root.val);
13    serialize_dfs(root.left, res);
14    serialize_dfs(root.right, res);
15}
16

Recommended Readings

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

Load More