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
-1e7 <= nums[i] <= 1e7

源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/partition-array-into-two-arrays-to-minimize-sum-difference
 

解法:

每个元素都有两种情况(加号,减号)一开始想的是传化为01背包问题,但元素的和很大,超出背包容量,并且背包做法无法确保等分数组。参考其它人题解,当数组长度不大(n<=20),可以枚举出所有情况。本题数组长度最大为30,无法直接枚举(2^{30})。故需用折半查找,那么一半的数组长度为15,则可以直接枚举。利用二进制来枚举,二进制数数位为1的,赋予加号,和为s1,减号和为s2.另一半加号和s3,减号和为s4.同时记录1的个数。具体查看代码

class Solution {

	void travel(vector<vector<int>> &v, int st, int n, vector<int> &nums)
	{
		for (int i = 0; i < (1 << n); i++)   //每个位置非正即负,可以用n位二进制来枚举
		{
			int sum = 0;
			int cnt = 0;
			for (int j = 0; j < n; j++)
			{
				if ((i >> j) & 1)    //第j位为1,赋予正号
				{
					sum += nums[st+j];    //st=0,表示枚举前半部分
					cnt++;    //记录正号个数
				}
				else
				{
					sum -= nums[st+j];  //第j位为0,赋予负号
				}
			}
			v[cnt].push_back(sum);   //记录正号个数为cnt个,数组和sum1=s1-s2。
		}
	}

public:
	int minimumDifference(vector<int>& nums) {
		int n = nums.size() / 2;
		vector<vector<int> > v1(n + 1);  //v1[i]表示正号个数为i时,其余为符号,数组和的情况
		travel(v1, 0, n, nums);  //枚举前一半。
		int ans = INT_MAX;
		for (int i = 0; i <= n; i++)sort(v1[i].begin(), v1[i].end());   //对前半部分排序,为了后半部分与前半部分匹配查找时更快速。
		for (int i = 0; i < (1 << n); i++)   //枚举后一半。
		{
			int sum = 0;  //后一半数组的和sum2=s3-s4
			int cnt = 0;  //正号个数
			for (int j = 0; j < n; j++)
			{
				if ((i >> j) & 1)
				{
					sum += nums[n + j];
					cnt++;
				}
				else
				{
					sum -= nums[n + j];
				}
			}
			int to = n - cnt;   //与前一半数组匹配,当后一半数组正号个数为cnt,则前一半为n-cnt;
            //求 s1-s2+s3-s4=sum1+sum2 的绝对值最小,那么就去查找与-sum2最相近的,就是sum1+sum2最接近0.
            //二分查找
			int id = lower_bound(v1[to].begin(), v1[to].end(), -sum) - v1[to].begin(); 
			if (id - 1 >= 0)
				ans = min(ans, abs(sum + v1[to][id - 1]));
			if (id < v1[to].size())
				ans = min(ans, abs(sum + v1[to][id]));
		}
		return ans;	
	}
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值