1681. 最小不兼容性
给你一个整数数组 nums
和一个整数 k
。你需要将这个数组划分到 k
个相同大小的子集中,使得同一个子集里面没有两个相同的元素。
一个子集的 不兼容性 是该子集里面最大值和最小值的差。
请你返回将数组分成 k
个子集后,各子集 不兼容性 的 和 的 最小值 ,如果无法分成分成 k
个子集,返回 -1
。
子集的定义是数组中一些数字的集合,对数字顺序没有要求。
示例 1:
```txt
输入:nums = [1,2,1,4], k = 2
输出:4
解释:最优的分配是 [1,2] 和 [1,4] 。
不兼容性和为 (2-1) + (4-1) = 4 。
注意到 [1,1] 和 [2,4] 可以得到更小的和,但是第一个集合有 2 个相同的元素,所以不可行。
```
示例 2:
```txt
输入:nums = [6,3,8,1,3,1,2,2], k = 4
输出:6
解释:最优的子集分配为 [1,2],[2,3],[6,8] 和 [1,3] 。
不兼容性和为 (2-1) + (3-2) + (8-6) + (3-1) = 6 。
```
示例 3:
```txt
输入:nums = [5,3,3,6,3,3], k = 3
输出:-1
解释:没办法将这些数字分配到 3 个子集且满足每个子集里没有相同数字。
```
提示:
">
题目
1681. 最小不兼容性
给你一个整数数组 nums
和一个整数 k
。你需要将这个数组划分到 k
个相同大小的子集中,使得同一个子集里面没有两个相同的元素。
一个子集的 不兼容性 是该子集里面最大值和最小值的差。
请你返回将数组分成 k
个子集后,各子集 不兼容性 的 和 的 最小值 ,如果无法分成分成 k
个子集,返回 -1
。
子集的定义是数组中一些数字的集合,对数字顺序没有要求。
示例 1:
1 2 3 4 5
| 输入:nums = [1,2,1,4], k = 2 输出:4 解释:最优的分配是 [1,2] 和 [1,4] 。 不兼容性和为 (2-1) + (4-1) = 4 。 注意到 [1,1] 和 [2,4] 可以得到更小的和,但是第一个集合有 2 个相同的元素,所以不可行。
|
示例 2:
1 2 3 4
| 输入:nums = [6,3,8,1,3,1,2,2], k = 4 输出:6 解释:最优的子集分配为 [1,2],[2,3],[6,8] 和 [1,3] 。 不兼容性和为 (2-1) + (3-2) + (8-6) + (3-1) = 6 。
|
示例 3:
1 2 3
| 输入:nums = [5,3,3,6,3,3], k = 3 输出:-1 解释:没办法将这些数字分配到 3 个子集且满足每个子集里没有相同数字。
|
提示:
-
1 <= k <= nums.length <= 16
-
nums.length
能被 k
整除。
1 <= nums[i] <= nums.length
题解
方法一:
思路
状压dp
定义为nums中选取下标集合为s的最小不兼容性。s用一个二进制数表示集合,二进制的第i位为1代表选取nums[i]
。
例如nums = [1,2,1,4]
,,这是因为,低位若从0开始,我们选取低位第2和第3位,得到
我们的状态转移可以从小集合向大集合转移。
假设对于集合s,比其小的集合t,状态均已求出。
那么我们只需枚举s的合法子集u(合法子集就是u的大小为n/k且u对于nums中不能有重复的数),并求出u中最大的nums值以及最小值,然后的最大值便是。
以这个思路实现了一份代码,不幸的是超时了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class Solution { public: const int INF = 0x3f3f3f3f; int minimumIncompatibility(vector<int>& nums, int k) { int n = nums.size(); sort(nums.begin(), nums.end()); vector<int> f(1<<n, INF); f[0] = 0; for (int i=1; i<1<<n; i++) { for (int j=i; j!=0; j = (j-1)&i) { int bc = 0, ok = 1, pre=-1, mx=-1, mn=20; for (int c=0; c<n; c++) { if (j>>c&1) { bc++; mx = max(mx, nums[c]); mn = min(mn, nums[c]); if (pre == nums[c]) ok = 0; else pre = nums[c]; } } if (ok && bc == n/k) f[i] = min(f[i], f[i^j]+mx-mn); } } return f[(1<<n)-1] == INF ? -1 : f[(1<<n)-1]; } };
|
现在分析这份代码的时间复杂度。
我们从小到大地枚举了内的数,我们将这些数视为二进制数,则可以认为是按照大小从小到大的枚举了集合。保证了在求大集合的状态时,其子集状态均已求出。
随后我们使用了二进制的技巧实现了枚举当前集合i的子集j。
1 2 3
| for (int j=i; j!=0; j = (j-1)&i) { }
|
然后又花了时间检查子集是否合法,并求出集合中的最大/最小值。
我们换一种思路来分析外两层循环的时间复杂度。
在中有只含1个1的二进制数有n个;只含两个1的二进制数有个;只含i个1的二进制数有个;
对于含i个1的二进制数其子集的个数又有个。根据二项式定理,外层两层循环次数
再记上内层循环,时间复杂度为。
考虑如何优化这份代码,实际上可以预处理出所有大小为n/k且不含重复数字的合法子集以及它们的最大值和最小值。预处理的时间是,但之后的只需便可判断子集是否合法以及获取最大/最小值。那么总时间复杂度就降到了。可以通过本题。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| class Solution { public: const int INF = 0x3f3f3f3f; int bit_cnt(int x) { int rt = 0; while (x) { rt++; x &= (x-1); } return rt; } int minimumIncompatibility(vector<int>& nums, int k) { int n = nums.size(); sort(nums.begin(), nums.end()); vector<int> ok(1<<n, 1); vector<int> mx(1<<n, -1); vector<int> mn(1<<n, 20); for (int i=0; i<1<<n; i++) { if (bit_cnt(i) != n/k) { ok[i] = 0; continue; } int pre = -1; for (int j=0; j<n; j++) { if (i>>j&1) { if (pre == nums[j]) { ok[i] = 0; break; } mx[i] = max(mx[i], nums[j]); mn[i] = min(mn[i], nums[j]); pre = nums[j]; } } }
vector<int> f(1<<n, INF); f[0] = 0; for (int i=1; i<1<<n; i++) { for (int j=i; j!=0; j = (j-1)&i) { if (ok[j]) f[i] = min(f[i], f[i^j]+mx[j]-mn[j]); } } return f[(1<<n)-1] == INF ? -1 : f[(1<<n)-1]; } };
|