1681. Minimum Incompatibility
问题:
给定一组数字,将其等分成k个个数相同的子数组。
求划分后,得到所有子数组最小差分和。
差分:子数组中,最大值-最小值
Example 1: Input: nums = [1,2,1,4], k = 2 Output: 4 Explanation: The optimal distribution of subsets is [1,2] and [1,4]. The incompatibility is (2-1) + (4-1) = 4. Note that [1,1] and [2,4] would result in a smaller sum, but the first subset contains 2 equal elements. Example 2: Input: nums = [6,3,8,1,3,1,2,2], k = 4 Output: 6 Explanation: The optimal distribution of subsets is [1,2], [2,3], [6,8], and [1,3]. The incompatibility is (2-1) + (3-2) + (8-6) + (3-1) = 6. Example 3: Input: nums = [5,3,3,6,3,3], k = 3 Output: -1 Explanation: It is impossible to distribute nums into 3 subsets where no two elements are equal in the same subset. Constraints: 1 <= k <= nums.length <= 16 nums.length is divisible by k 1 <= nums[i] <= nums.length
解法:
解法一:Backtracking(回溯算法)->TLE
- 状态:到目前为止,选择后的结果path,以及选择后剩下元素的个数count
- 选择:任意4个不同的元素(组合)(count(元素i)!=0)
- 递归退出条件:path.size==num.size/k 获得划分完毕的组数
代码参考:
1 #define CNTSIZE 17 2 class Solution { 3 public: 4 // int count = 0; 5 // void print_intend() { 6 // for(int i=0; i<count; i++) { 7 // printf(" "); 8 // } 9 // } 10 // void print_path(vector<vector<int>>& path) { 11 // print_intend(); 12 // printf("PATH: {"); 13 // for(vector<int> aSet: path) { 14 // printf("{%d, %d} ", aSet[1], aSet[0]); 15 // } 16 // printf("}\n"); 17 // } 18 // void print_cout(int (&cout)[CNTSIZE]) { 19 // print_intend(); 20 // printf("cout: {"); 21 // for(int i=0; i<CNTSIZE; i++){ 22 // if(cout[i]!=0) { 23 // printf("[%d]=%d, ",i,cout[i]); 24 // } 25 // } 26 // printf("}\n"); 27 // } 28 // void print_opt(vector<int>& opt) { 29 // print_intend(); 30 // printf("opt: {"); 31 // for(int i:opt){ 32 // printf("%d, ",i); 33 // } 34 // printf("}\n"); 35 // } 36 int subsize=0; 37 int incomp(vector<vector<int>>& path) { 38 int res = 0; 39 for(vector<int> aSet: path) { 40 res += aSet[1] - aSet[0]; 41 } 42 return res; 43 } 44 bool isValid(int mask, int (&cout)[CNTSIZE], vector<int>& opt) { 45 for(int i=0; i<CNTSIZE; i++) { 46 if((mask>>i&1) == 1) { 47 if(cout[i]==0) return false; 48 opt.push_back(i); 49 } 50 } 51 if(opt.size()<=0) return false; 52 return true; 53 } 54 void chooseOpt(vector<vector<int>>& path, int (&cout)[CNTSIZE], vector<int>& opt) { 55 int minV = opt[0], maxV = opt[0]; 56 for(int i=0; i<opt.size(); i++) { 57 minV = min(minV, opt[i]); 58 maxV = max(maxV, opt[i]); 59 cout[opt[i]]--; 60 } 61 path.push_back({minV, maxV}); 62 return; 63 } 64 void resetOpt(vector<vector<int>>& path, int (&cout)[CNTSIZE], vector<int>& opt) { 65 path.pop_back(); 66 for(int i=0; i<opt.size(); i++) { 67 cout[opt[i]]++; 68 } 69 return; 70 } 71 void backtrack(int& res, vector<vector<int>>& path, int (&cout)[CNTSIZE], int k) { 72 if(path.size()==k) { 73 // print_intend(); 74 // printf("before res: %d.\n", res); 75 res = min(res, incomp(path)); 76 // print_intend(); 77 // printf("after res: %d.\n", res); 78 return; 79 } 80 int mask = 1<<CNTSIZE; 81 for(int i=0; i<mask; i++) { 82 vector<int> opt; 83 if(__builtin_popcount(i) == subsize && isValid(i, cout, opt)) { 84 //get one combination 85 chooseOpt(path, cout, opt); 86 // print_intend(); 87 // printf("subsie:%d, mask_i:%d\n", subsize, i); 88 // print_opt(opt); 89 // print_path(path); 90 // print_cout(cout); 91 // count++; 92 backtrack(res, path, cout, k); 93 // count--; 94 resetOpt(path, cout, opt); 95 } 96 } 97 // print_intend(); 98 // printf("return... \n"); 99 return; 100 } 101 int minimumIncompatibility(vector<int>& nums, int k) { 102 int res = INT_MAX; 103 subsize = nums.size()/k; 104 vector<vector<int>> path; 105 int cout[CNTSIZE]={0}; 106 for(int n:nums){ 107 cout[n]++; 108 } 109 backtrack(res, path, cout, k); 110 return (res==INT_MAX?-1:res); 111 } 112 };
解法二:dp结果保存
dp[map]:选择状态为map的时候,做任意分组,可获得的最小差分和结果
num.size的元素,提取任意个数,做组合:从0->(1<<num.size-1)
其中选择个数为subsize=num.size/k 的组合为要求的子数组的一个选择。
首先初始化,任意subsize个元素构成子数组后的,最小差分和结果:
1 for(int i=0; i<(1<<n); i++) { 2 if(__builtin_popcount(i) == subsize) { 3 set<int> grp; 4 for(int j=0; j<n; j++) { 5 if(((i>>j)&1)==1) grp.insert(nums[j]); 6 } 7 if(grp.size()==subsize) dp[i] = *prev(grp.end()) - *grp.begin(); 8 } 9 }
然后 从选择元素个数>一个子数组subsize个之后开始,逐次计算
当前选择元素状态下的,最小差分和。
i:从2组子数组的选择开始:
j:在 i 的这种构成中,其中获得一个组 j,可得到的一个选择结果。
若:这个组 j 没有结果 || 除去这个组剩下元素构成的组 i-j 也没有结果 || j 没构成一个组(j的元素数!=subsize)
那么这个选择无效,跳过。
否则:dp[i] = dp[j] + dp[i-j]
若dp[i]以及保存过解,那么说明,之前 i 的这种构成中,已经找到过一个合法的 分裂组合 j 和 i-j。那么在现在的结果和新结果中求最小:min(dp[i], dp[j]+dp[i-j])
最终求得,dp[11111111...1] 所有元素被选择完,得到的最优解。
代码参考:
1 class Solution { 2 public: 3 int subsize=0; 4 int minimumIncompatibility(vector<int>& nums, int k) { 5 int res = INT_MAX; 6 int n = nums.size(); 7 subsize = n/k; 8 vector<int>dp(1<<n, INT_MAX); 9 //initialize the incomp of each kind of grp. 10 for(int i=0; i<(1<<n); i++) { 11 if(__builtin_popcount(i) == subsize) { 12 set<int> grp; 13 for(int j=0; j<n; j++) { 14 if(((i>>j)&1)==1) grp.insert(nums[j]); 15 } 16 if(grp.size()==subsize) dp[i] = *prev(grp.end()) - *grp.begin(); 17 } 18 } 19 20 for(int i=0; i<(1<<n); i++) { 21 if(__builtin_popcount(i) % subsize != 0 || __builtin_popcount(i) == subsize) 22 continue; 23 //处理:整组,&& 是2组以及2组以上 24 for(int j=i; j; j=(j-1)&i) {//当前情况的内部:j构成一组,处理 25 if(__builtin_popcount(j) != subsize || dp[j] == INT_MAX || dp[i-j] == INT_MAX) 26 continue; 27 dp[i] = min(dp[i], dp[j] + dp[i-j]); 28 } 29 } 30 res = dp[(1<<n)-1]; 31 return res==INT_MAX?-1:res; 32 } 33 };