[Leetcode]5897. 将数组分成两个数组并最小化数组和的差
【题目描述如下】
给你一个长度为 2 * n 的整数数组。你需要将 nums 分成 两个 长度为 n 的数组,分别求出两个数组的和,并 最小化 两个数组和之 差的绝对值 。nums 中每个元素都需要放入两个数组之一。
请你返回 最小 的数组和之差。
示例 1:
输入:nums = [3,9,7,3]
输出:2
解释:最优分组方案是分成 [3,9] 和 [7,3] 。
数组和之差的绝对值为 abs((3 + 9) - (7 + 3)) = 2 。
示例 2:
输入:nums = [-36,36]
输出:72
解释:最优分组方案是分成 [-36] 和 [36] 。
数组和之差的绝对值为 abs((-36) - (36)) = 72 。
示例 3:
输入: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
-107 <= nums[i] <= 107
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/partition-array-into-two-arrays-to-minimize-sum-difference
【题目分析】
刚开始看到这个题目,感觉很熟悉,状态压缩嘛,于是很开心的做了起来。代码如下:
class Solution {
public:
/*
每个数都有放在数组1或数组2两种选择,所以一共有2^2n-1中可能,由于是等分为两个数组,
中间要去掉不能等分的;采用状态压缩法,用二进制表示每一种分组;
*/
int BitCount(unsigned int n)
{
unsigned int c = 0 ;
while (n > 0) {
c += n & 1;
n >>= 1;
}
return c ;
}
int CalcSum(vector<int>& nums, unsigned int n)
{
int res = 0;
int i = 0;
while (n > 0) {
if (n & 1) {
res += nums[i];
}
i++;
n >>= 1;
}
return res;
}
int minimumDifference(vector<int>& nums) {
int res = INT_MAX;
int n = nums.size();
vector<bool> rec((1 << n), false);
for (int i = (1 << (n / 2)) - 1; i < 1 << n; i++) {
if (BitCount(i) == n / 2) {
// 求补集
int left = i ^ ((1 << n) - 1);
res = min(res, abs(CalcSum(nums, i) - CalcSum(nums, left)));
}
}
return res;
}
};
每个数都有放在数组1或数组2两种选择,所以一共有2^2n-1中可能,由于是等分为两个数组,中间要去掉不能等分的;采用状态压缩法,用二进制表示每一种分组; 由于n最大为15,上述代码的时间复杂度O(2^2n),代码会超时;
后来看了题解,发现使用分段的状态压缩和二分查找很巧妙地降低时间复杂度为O(n*2^n);
代码分三步:
1)首先在前半段(n)进行状态枚举,每个数有两种情况:放到数组1,即加上其值;放到数组2,即减去其值。这样可以求出后半段的所有组合,存入数组f;
同理针对后半段采用同样的策略,将所有的组合放入数组g;
2)分别对f、g中的数组进行升序排列,以便后续二分查找;
3)所求最小差值为min(abs(f[i] + g[n-i])),遍历每个f[i],同时用二分法查找绝对值和最小的g[n-i];
i + n - i = n表示等分的两个数组,每个有n个数;
代码如下:
class Solution {
public:
/*
每个数都有放在数组1或数组2两种选择,所以一共有2^2n-1中可能,由于是等分为两个数组,
中间要去掉不能等分的;采用状态压缩法,用二进制表示每一种分组;
由于n最大为15,O(2^2n)会导致代码超时;
所以分成两段进行求解,将时间复杂度降至O(n*2^n);
*/
int BitCount(unsigned int n)
{
unsigned int c = 0 ;
while (n > 0) {
c += n & 1;
n >>= 1;
}
return c ;
}
int minimumDifference(vector<int>& nums) {
int res = INT_MAX;
int n = nums.size() / 2;
vector<vector<int>> f(n + 1);
vector<vector<int>> g(n + 1);
for (int i = 0; i < (1 << n); i++) {
int cnt = BitCount(i);
int j = 0;
int sum = 0;
int mask = i;
while (j < n) {
if (mask & 0x1) {
sum += nums[j];
} else {
sum -= nums[j];
}
mask >>= 1;
j++;
}
f[cnt].emplace_back(sum);
}
for (int i = 0; i < (1 << n); i++) {
int cnt = BitCount(i);
int j = 0;
int sum = 0;
int mask = i;
while (j < n) {
if (mask & 0x1) {
sum += nums[n + j];
} else {
sum -= nums[n + j];
}
mask >>= 1;
j++;
}
g[cnt].emplace_back(sum);
}
// 升序排列,为了方便后面二分查找
for (int i = 0; i < n; i++) {
sort(begin(f[i]), end(f[i]));
sort(begin(g[i]), end(g[i]));
}
// 最小差值即为min(abs(f[i] + g[n-i]))
for (int i = 0; i < n; i++) {
for (auto& ft : f[i]) {
// 查找大于等于-ft的,以便绝对值和最小
auto it = lower_bound(begin(g[n - i]), end(g[n - i]), -ft);
// 如果找到了
if (it != end(g[n - i])) {
res = min(res, abs(ft + *it));
}
// 如果it == end没找到,则取最接近的
if (it != begin(g[n - i])) {
res = min(res, abs(ft + *prev(it)));
}
}
}
return res;
}
};