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

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

```txt
输入:nums = [2,-1,0,4,-2,-9]
输出:0
解释:最优分组方案是分成 [2,4,-9] 和 [-1,0,-2] 。
数组和之差的绝对值为 abs((2 + 4 + -9) - (-1 + 0 + -2)) = 0 。
```
提示:
">
题目
2035. 将数组分成两个数组并最小化数组和的差
给你一个长度为 2 * n
的整数数组。你需要将 nums
分成 两个 长度为 n
的数组,分别求出两个数组的和,并 最小化 两个数组和之 差的绝对值 。nums
中每个元素都需要放入两个数组之一。
请你返回 最小 的数组和之差。
示例 1:
![example-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:
![example-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 <= n <= 15
-
nums.length == 2 * n
-10^7 <= nums[i] <= 10^7
题解
方法一:
思路
折半搜索+二分。
注意到数据量极小(n=30),但是每个数的数值范围极大(m=1e9)。
如果数据量不太小(n=1e3),所有数的总和范围不太大(sum(m)=1e3),可以用01背包解决。时间复杂度$O(n*sum(m))$
这里就利用数据个数极小这一点进行优化。
利用折半搜索和二分查找,将时间复杂度降低到$O(2^{n/2}*log(2^{n/2}))$
具体做法是
将前n/2个数枚举选取的组合,对于选取的一部分a认为是放到第一个数组中,对于没有选取的一部分b认为是放到第二个数组中。
将a个数求和-b个数求和存入有序集合中。
然后再对后n/2个数枚举组合。在有序集合中寻找一个值与之和最靠近0,这可以用二分查找。
代码
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
| class Solution { public: using ll = long long; int minimumDifference(vector<int>& nums) { int n = nums.size()/2; set<ll> st[n+1]; for (int i=0; i<1<<n; i++) { ll cnt = 0, cb = 0; for (int j=0; j<n; j++) { if (i>>j&1) { cnt += nums[j]; cb++; } else { cnt -= nums[j]; } } st[cb].insert(cnt); } ll ans = 1e15; for (int i=0; i<1<<n; i++) { ll cnt = 0, cb = 0; for (int j=0; j<n; j++) { if (i>>j&1) { cnt += nums[j+n]; } else { cnt -= nums[j+n]; cb++; } } auto it = st[cb].lower_bound(-cnt); if (it != st[cb].end()) { ans = min(ans, abs(*it+cnt)); } if (it != st[cb].begin()) { ans = min(ans, abs(*--it+cnt)); } } return ans; } };
|
方法二:
思路
折半搜索,枚举前n个数的组合情况(选取x个的和减去剩余的n-x个的和cnt)存入第x个平衡树,注意到要将2n长度的数组分割成两个长度为n的数组。第二次枚举时当选取x个负数时,和为cnt,可以在第x个平衡树中二分查找离-cnt最近的数
同理也可以转化为n+1对有序列表,进行n+1次双指针。
代码
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
| class Solution { public: int minimumDifference(vector<int>& nums) { int n = nums.size(); long sum = accumulate(nums.begin(), nums.end(), 0L); vector<vector<long>> a(n+1), b(n+1); for (int i=0; i<(1<<n/2); i++) { long cnt = 0, w = 0; for (int j=0; j<n/2; j++) { if (i>>j&1) { cnt += nums[j]; w++; } else { cnt -= nums[j]; } } a[w].push_back(cnt); } long ans = 0x3f3f3f3f; for (int i=0; i<(1<<n/2); i++) { long cnt = 0, w = 0; for (int j=0; j<n/2; j++) { if (i>>j&1) { cnt += nums[j+n/2]; } else { cnt -= nums[j+n/2]; w++; } } b[w].push_back(cnt); } for (int i=0; i<=n; i++) { sort(a[i].begin(), a[i].end()); sort(b[i].begin(), b[i].end()); int l = 0, r = (int) b[i].size()-1; while (l<a[i].size() && r>=0) { ans = min(ans, abs(a[i][l] + b[i][r])); if (a[i][l] + b[i][r] > 0) r--; else l++; } } return ans; } };
|