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.
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:
- Is
n
divisible byk
? - 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:
-
Check divisibility: First, we calculate the quotient
m
and remaindermod
when dividing the length of the arrayn
byk
:m, mod = divmod(len(nums), k)
If
mod
is not zero (meaningn
is not divisible byk
), we immediately returnFalse
since we cannot form complete groups. -
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.
-
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 returnFalse
. Otherwise, we returnTrue
.
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 EvaluatorExample 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)
takesO(1)
time divmod(len(nums), k)
takesO(1)
timeCounter(nums)
iterates through alln
elements once to count their frequencies, which takesO(n)
timeCounter(nums).values()
returns a view of the values inO(1)
timemax(Counter(nums).values())
iterates through all unique values in the counter to find the maximum, which in the worst case isO(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 inO(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)
whereM
is the number of distinct elements innums
, andM ≤ 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:
- Empty array:
nums = []
with any k - k larger than array length:
nums = [1,2]
withk = 3
- k equals 1: Every element forms its own group
- All elements are identical:
nums = [1,1,1,1]
withk = 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
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
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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!