【每日一题】1681. 最小不兼容性

文章介绍了如何使用动态规划和状态压缩的方法解决最小不兼容性问题,即给定一个数组和目标子集数k,找到使各子集最大值与最小值之差之和最小的划分方式。当无法满足子集中无重复元素的条件时,返回-1。文中给出了具体的解题思路和代码实现。
摘要由CSDN通过智能技术生成

【每日一题】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)&sub;
            }
        }
        return (dp[(1<<n)-1]<INT_MAX)?dp[(1<<n)-1]:-1;
    }
};

总结:看着题解,带入例子nums=[1,2,1,4],k=2,能够理解大致意思,但是下次遇到估计还是不会写,不断总结,不断成长,慢慢沉淀吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值