You are given an integer array nums
and an integer k
. You are asked to distribute this array into k
subsets of equal size such that there are no two equal elements in the same subset.
A subset's incompatibility is the difference between the maximum and minimum elements in that array.
Return the minimum possible sum of incompatibilities of the k
subsets after distributing the array optimally, or return -1
if it is not possible.
A subset is a group integers that appear in the array with no particular order.
Example 1:
Input: nums = [1,2,1,4], k = 2 Output: 4 Explanation: The optimal distribution of subsets is [1,2] and [1,4]. The incompatibility is (2-1) + (4-1) = 4. Note that [1,1] and [2,4] would result in a smaller sum, but the first subset contains 2 equal elements.
Example 2:
Input: nums = [6,3,8,1,3,1,2,2], k = 4 Output: 6 Explanation: The optimal distribution of subsets is [1,2], [2,3], [6,8], and [1,3]. The incompatibility is (2-1) + (3-2) + (8-6) + (3-1) = 6.
Example 3:
Input: nums = [5,3,3,6,3,3], k = 3 Output: -1 Explanation: It is impossible to distribute nums into 3 subsets where no two elements are equal in the same subset.
Constraints:
1 <= k <= nums.length <= 16
nums.length
is divisible by k
1 <= nums[i] <= nums.length
Given an integer array nums
and an integer k
, the task is to distribute nums
into k
subsets of equal size such that no two equal elements are in the same subset. The incompatibility of a subset is the difference between its maximum and minimum elements. The goal is to find the minimum possible sum of incompatibilities across all k
subsets. If it's impossible to distribute the array, return -1.
This solution uses a combination of preprocessing, state compression, and dynamic programming to efficiently find the minimum incompatibility.
1. Preprocessing:
g[i]
for each possible subset i
. i
is represented as a bitmask where the j-th bit is 1 if the j-th element of nums
is included in the subset. The code checks if the subset has the correct size (m = n/k
) and contains no duplicates. If valid, it calculates the incompatibility.2. State Compression:
i
to represent the state of the partitioning. Each bit in i
corresponds to an element in nums
. A bit set to 1 indicates the element is assigned to a subset; 0 indicates it's unassigned.3. Dynamic Programming:
DP Table: f[i]
stores the minimum incompatibility sum for the state represented by i
. f[0] = 0
(no elements assigned).
Iteration: The code iterates through each state i
. For each unassigned element, it explores all possible subsets of the unassigned elements (mask
). If a valid subset is found (correct size, no duplicates), it updates the DP table:
f[i | j] = min(f[i | j], f[i] + g[j])
This means the minimum incompatibility for the new state (with subset j
added) is the minimum of the current value and the sum of the incompatibility of j
and the minimum incompatibility of the previous state i
.
Final Result: f[(1 << n) - 1]
represents the minimum incompatibility when all elements are assigned. If this value is still infinity (meaning no valid partitioning was found), return -1; otherwise, return the value.
Time Complexity: O(3n) - The dominant factor is the three nested loops: the outer loop iterates through all possible states (2n), the inner loop explores subsets (mask
), and within that loop it iterates through subsets j
. While not exactly 3n, the complexity is exponential and closely related to it.
Space Complexity: O(2n) - The space used by the DP table f
and the pre-calculated incompatibilities g
is proportional to 2n.
class Solution:
def minimumIncompatibility(self, nums: List[int], k: int) -> int:
n = len(nums)
m = n // k
g = [-1] * (1 << n)
for i in range(1, 1 << n):
if i.bit_count() != m:
continue
s = set()
mi, mx = 20, 0
for j, x in enumerate(nums):
if i >> j & 1:
if x in s:
break
s.add(x)
mi = min(mi, x)
mx = max(mx, x)
if len(s) == m:
g[i] = mx - mi
f = [inf] * (1 << n)
f[0] = 0
for i in range(1 << n):
if f[i] == inf:
continue
s = set()
mask = 0
for j, x in enumerate(nums):
if (i >> j & 1) == 0 and x not in s:
s.add(x)
mask |= 1 << j
if len(s) < m:
continue
j = mask
while j:
if g[j] != -1:
f[i | j] = min(f[i | j], f[i] + g[j])
j = (j - 1) & mask
return f[-1] if f[-1] != inf else -1
The code in other languages (Java, C++, Go, TypeScript, C#) follows a similar structure and logic. The primary differences are in syntax and the handling of bit manipulation and data structures. The core DP algorithm remains consistent across all implementations.