【每日一题】1681. 最小不兼容性
1681. 最小不兼容性
题目描述
给你一个整数数组 nums 和一个整数 k 。你需要将这个数组划分到 k 个相同大小的子集中,使得同一个子集里面没有两个相同的元素。
一个子集的 不兼容性 是该子集里面最大值和最小值的差。
请你返回将数组分成 k 个子集后,各子集 不兼容性 的 和 的 最小值 ,如果无法分成分成 k 个子集,返回 -1 。
子集的定义是数组中一些数字的集合,对数字顺序没有要求。
示例 1:
输入:nums = [1,2,1,4], k = 2
输出:4
解释:最优的分配是 [1,2] 和 [1,4] 。
不兼容性和为 (2-1) + (4-1) = 4 。
注意到 [1,1] 和 [2,4] 可以得到更小的和,但是第一个集合有 2 个相同的元素,所以不可行。
示例 2:
输入: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:
输入:nums = [5,3,3,6,3,3], k = 3
输出:-1
解释:没办法将这些数字分配到 3 个子集且满足每个子集里没有相同数字。
提示:
1 <= k <= nums.length <= 16
nums.length 能被 k 整除。
1 <= nums[i] <= nums.length
解题思路
思路:动态规划+状态压缩。注意到,数组长度以及数组元素的范围均在1到16之间,我们可以使用mask表示子集,其中mask的每一位表示的是数组元素下标。首先我们遍历所有可能的子集集合,求出满足分组个数的子集mask,并判断该子集中是否有元素重复,其可以通过一个set集合来实现,如果各元素均不重复则求出该子集的不兼容性。然后我们遍历全集去掉mask剩余且不重复的集合sub,其可以通过一个map来实现,再通过求解sub的子集来不断更新递推公式,dp[mask|nxt]=min(dp[mask|nxt],dp[mask]+values[nxt])。注意,起始从0开始,故可以推导出很多子集片段,等到遍历到后续的子集时,再通过sub,可以实现子集与子集的拼接来看看是否能够拼凑出全集。
class Solution {
public:
int minimumIncompatibility(vector<int>& nums, int k) {
int n=nums.size();
int group=n/k;
//dp[mask]表示元素分配为mask时元素集合的不兼容性之和
vector<int> dp(1<<n,INT_MAX);
dp[0]=0;
//values[mask]表示元素分配为mask时的不兼容性
unordered_map<int,int> values;
//遍历分配情况 mask表示子集!
for(int mask=1;mask<(1<<n);mask++)
{
//判断每一个分组中1的个数
if(__builtin_popcount(mask)!=group)
continue;
int mn=20,mx=0;
//判断对应元素是否在该子集中 从而避免重复
unordered_set<int> cur;
//遍历数组下标
for(int i=0;i<n;i++)
{
//判断该值是否在该子集中
if(mask&(1<<i))
{
//该值出现过则跳过该子集
if(cur.count(nums[i])>0)
break;
//否则加入到cur并更新最大最小值
cur.insert(nums[i]);
mn=min(mn,nums[i]);
mx=max(mx,nums[i]);
}
}
if(cur.size()==group)
{
values[mask]=mx-mn;
//cout<<mask<<":"<<values[mask]<<endl;
}
}
for(int mask=0;mask<(1<<n);mask++)
{
//mask本身不满足子集则跳过
if(dp[mask]==INT_MAX)
continue;
//保留尚未被分配的集合 相等元素只出现最后一个
unordered_map<int,int> seen;
for(int i=0;i<n;i++)
if((mask&(1<<i))==0)
seen[nums[i]]=i;
//尚未分配的集合不够一个分组数量则跳过
if(seen.size()<group)
continue;
int sub=0;
//将未被分配的元素加入sub中
for(auto &pair:seen)
sub|=(1<<pair.second);
//sub是全集去掉mask剩余且不重复的
int nxt=sub;
//直到0
while(nxt>0)
{
if(values.count(nxt)>0)
dp[mask|nxt]=min(dp[mask|nxt],dp[mask]+values[nxt]);
//用于生成下一个子集
nxt=(nxt-1)⊂
}
}
return (dp[(1<<n)-1]<INT_MAX)?dp[(1<<n)-1]:-1;
}
};
总结:看着题解,带入例子nums=[1,2,1,4],k=2,能够理解大致意思,但是下次遇到估计还是不会写,不断总结,不断成长,慢慢沉淀吧!