1681.最小不兼容性 题解(java)
写在前面:笔试的时候考了这道题,当时暴力过了80,然后在leetcode上找到了原题,在此记录下题目的思路,感觉还蛮有意思的——>其实就是暴力我觉得/捂脸
1681.最小不兼容性
题目描述:
-
给你一个整数数组 nums 和一个整数 k 。你需要将这个数组划分到 k 个相同大小的子集中,使得同一个子集里面没有两个相同的元素。
一个子集的 不兼容性 是该子集里面最大值和最小值的差。
请你返回将数组分成 k 个子集后,各子集 不兼容性 的 和 的 最小值 ,如果无法分成分成 k 个子集,返回 -1 。
子集的定义是数组中一些数字的集合,对数字顺序没有要求。
解题思路:
- 官方把这个叫做状态压缩,但是我觉得就是二进制的穷尽,然后挑选条件合适的留下,然后开始第二次穷举
- 具体说明:就是,我们首先获得数组的长度,然后根据这个长度得到一个2n的数字,这些数字的二进制表达方式标志着数组中的每一个位置选或者不选,比如数组长度为2,则有22=4个数字,也就是0,1,2,3,然后二进制表示为[0,0],[0,1],[1,0],[1,1],这正好表示了四种选择方式;既然我们获取到了所有的选择方式,我们就可以开始筛选数据了,符合题意的条件的排列如下:1)每个子集必须是n/k的长度;2)每个子集里面不能有重复的数字,那么我们就可以写出代码了,因为Integer包里有一个bitCount方法,可以计算二进制表示下有多少个1,这就可以筛选出所有n/k长度的子集,然后第二步就是不能有重复的数字,这个我们在遍历的时候用nums数组(长度为原数组长度,因为题目给的数据范围,最大的数不可能超过数组长度)记录每个数字出现的次数,如果大于1就break,这样不符合条件的位置全部置为-1,我们就得到了一个“符合条件的子集”的数组
- 然后我们就可以开始dp了,其实称之为记忆化搜索更好一点,具体思路是:我们定义一个dp数组,长度为2^n,也就是说我们在接下来的步骤会递推每一种选择方式,然后递推到全是1的位置之后,这个位置就是答案(因为符合题意得答案,就是要选择所有的数据,然后既要符合条件,又要最优),所以我们可以写出dp的代码,首先是要判断当前位置选中的元素的数量是否是n/k的倍数,因为要均匀的分布在k个数组中,所以不是n/k的倍数的全部置为-1即可(即不符合题意),然后开始判断,只有当之前的子集数组的值不为-1,以及和它的选择互斥的dp数组相应位置不为-1,才进入下一步,下一步判断dp当前位置是否为-1,如果为-1则说明是第一次,需要初始化,值为子集的最大兼容性加上当前位置范围内与其互斥的dp位置的最大兼容性的和,然后如果不是-1,就说明需要取最小值,然后继续循环,循环的下一个位置是(sub-1)%i,什么意思呢,就是只选i中有的元素(为1的项),如果它小于0,则说明退出循环了,不用管其中的元素个数是不是n/k的倍数,因为如果不是倍数,在上一次的子集选择中已经置为-1了,达不到进入下一步操作的条件,这样循环之后,就可以得到最终的一个最优方案(其实就是遍历所有的元素,只不过加了筛选)-->具体看代码,写的思路是我脑子里的思路,可能第三方会有些看不懂,代码还是最直接的
实现代码:
class Solution {
public int minimumIncompatibility(int[] nums, int k) {
int n = nums.length;
int total = 1 << n;//算出所有可能的子集数量
int[] value = new int[total];
int max_size = n/k;
for(int i = 0;i<total;++i){
if(Integer.bitCount(i)==max_size){//先要满足个数
//满足个数之后要满足每个数字都不一样,然后只记录最大兼容性
int[] temp = new int[n+1];//最多16个数字
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
boolean flag = false;
for(int j = 0;j<n;++j){
if(((i>>j)&1)==1){
//说明当前数字被选中了
++temp[nums[j]];
if(temp[nums[j]]>1){
flag = true;
break;
}
min = Math.min(min,nums[j]);
max = Math.max(max,nums[j]);
}
}
if(flag){
//说明有数字重复了
value[i] = -1;
}else{
value[i] = max-min;
}
}else{
value[i] = -1;
}
}
//开始dp
int[] dp = new int[total];//从0开始递推,计算每一个方案下面的最小 最大兼容性(每一个index代表一个方案)
for(int i = 1;i<total;++i){
dp[i] = -1;
if(Integer.bitCount(i)%max_size ==0){
//因为要计算多种方案,所以需要是max_size的倍数
int sub = i;
while(sub>0){
if(value[sub]!=-1&&dp[i^sub]!=-1){//如果互斥项为-1,也没有必要继续下去了
if(dp[i]==-1){
//说明是第一个,需要初始化
dp[i] = value[sub] + dp[i ^ sub];//找到互斥的最优记录
}else{
dp[i] = Math.min(dp[i],value[sub]+dp[i ^ sub]);//不断更新,找到当前的最优项目
}
}
sub = (sub-1)&i;
}
}
}
return dp[total-1];
}
}
以上
希望对后来者有所帮助