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];
    }
}

以上
希望对后来者有所帮助

posted @ 2022-09-01 09:42  醉生梦死_0423  阅读(42)  评论(0编辑  收藏  举报