阿犇

记录生活中的点点滴滴

0%

第262场双周赛D

Trace

将数组分成两个数组并最小化数组和的差

传送门:https://leetcode-cn.com/problems/partition-array-into-two-arrays-to-minimize-sum-difference/

Problem

给你一个长度为 2 * n 的整数数组。你需要将 nums 分成 两个 长度为 n 的数组,分别求出两个数组的和,并 最小化 两个数组和之 差的绝对值nums 中每个元素都需要放入两个数组之一。

请你返回 最小 的数组和之差。

示例 1:

1
2
3
4
输入:nums = [3,9,7,3]
输出:2
解释:最优分组方案是分成 [3,9] 和 [7,3] 。
数组和之差的绝对值为 abs((3 + 9) - (7 + 3)) = 2 。

示例 2:

1
2
3
4
输入:nums = [-36,36]
输出:72
解释:最优分组方案是分成 [-36] 和 [36] 。
数组和之差的绝对值为 abs((-36) - (36)) = 72 。

示例 3:

1
2
3
4
输入:nums = [2,-1,0,4,-2,-9]
输出:0
解释:最优分组方案是分成 [2,4,-9] 和 [-1,0,-2] 。
数组和之差的绝对值为 abs((2 + 4 + -9) - (-1 + 0 + -2)) = 0 。

提示:

1
2
3
1 <= n <= 15
nums.length == 2 * n
-10^7 <= nums[i] <= 10^7

思路

最接近目标值的子序列和问题,与

https://www.aben.fun/2021/11/02/%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9B%AE%E6%A0%87%E5%80%BC%E7%9A%84%E5%AD%90%E5%BA%8F%E5%88%97%E5%92%8C/#more

非常类似。

搜索所有子集的时间复杂度较大的时候,可以考虑把原始集合划分成两个相等长度的集合,分别求子集。

这里把原数组划分成$sum1、sum2$两个长度为$n$的集合,分别求出其中所有子集的和(位运算),按组成子集的的元素个数分组。

然后开始合并两个子集,由于固定合并之后的长度为$n$,所以需要遍历$sum1$中组成元素个数从$0-n$的子集(对应$sum2$中为($n-i$)),可以用双指针或者二分求出于$sum/2$的最小差值。

将所有的元素*2是为了避免$sum/2$不是整数。

程序

折半搜索+状态压缩+双指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class Solution {
public:

int getMin(vector<int>& sum1,vector<int>& sum2,int halfsum){
int Min=0x3f3f3f3f;
int i=0,j=sum2.size()-1;

while(i<sum1.size()&&j>=0){
Min=min(Min,abs(sum1[i]+sum2[j]-halfsum));
if(sum1[i]+sum2[j]>=halfsum){
j--;
}
else{
i++;
}
}
return Min;
}
int minimumDifference(vector<int>& nums) {
// 保证sum/2为整数
int halfsum=0;
for(int i=0;i<nums.size();i++){
halfsum+=nums[i];
nums[i]*=2;
}
int n=nums.size()/2;
vector<vector<int>> sum1(n+1);
vector<vector<int>> sum2(n+1);

for(int i=0;i<(1<<n);i++){
int s=0;
int k=0;
for(int j=0;j<n;j++){
if(i&(1<<j)){
k++;
s+=nums[j];
}
}
sum1[k].push_back(s);
}
for(int i=0;i<(1<<n);i++){
int s=0;
int k=0;
for(int j=0;j<n;j++){
if(i&(1<<j)){
k++;
s+=nums[n+j];
}
}
sum2[k].push_back(s);
}

// 问题变成了从16组两个元素个数不超过C(7,15)的列表中找出和最接近原来总和一半的方案,取得的两组的元素和为n

for(auto& sum:sum1){
sort(sum.begin(),sum.end());
}
for(auto& sum:sum2){
sort(sum.begin(),sum.end());
}
int Min=0x3f3f3f3f;
for(int i=0;i<=n;i++){
Min=min(Min,getMin(sum1[i],sum2[n-i],halfsum));
if(Min==0){
break;
}
}
return Min;
}
};
折半搜索+状态压缩+二分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class Solution {
public:

int minimumDifference(vector<int>& nums) {
// 保证sum/2为整数
int halfsum=0;
for(int i=0;i<nums.size();i++){
halfsum+=nums[i];
nums[i]*=2;
}
int n=nums.size()/2;
vector<vector<int>> sum1(n+1);
vector<vector<int>> sum2(n+1);

for(int i=0;i<(1<<n);i++){
int s=0;
int k=0;
for(int j=0;j<n;j++){
if(i&(1<<j)){
k++;
s+=nums[j];
}
}
sum1[k].push_back(s);
}
for(int i=0;i<(1<<n);i++){
int s=0;
int k=0;
for(int j=0;j<n;j++){
if(i&(1<<j)){
k++;
s+=nums[n+j];
}
}
sum2[k].push_back(s);
}

int Min=0x3f3f3f3f;
// 问题变成了从16组两个元素个数不超过C(7,15)的列表中找出和最接近原来总和一半的方案,取得的两组的元素和为n
// 二分
// 对sum2进行排序,对sum1中的每个i中的值,二分sum2中的n-i
for(auto& sum:sum2){
sort(sum.begin(),sum.end());
}
for(int i=0;i<sum1.size();i++){
auto x=sum1[i];
auto y=sum2[n-i];
for(auto num:x){
int l=0,r=y.size()-1;
while(l<r){
int mid=(l+r)>>1;
if(num+y[mid]-halfsum>=0){
r=mid;
}
else{
l=mid+1;
}
}
Min=min(Min,abs(num+y[l]-halfsum));
}
}
return Min;
}
};
您的支持是我继续创作的最大动力!