将数组分成两个数组并最小化数组和的差(折半枚举)

博客探讨了一种高效的算法策略,用于将一个数组分成两个子数组,以最小化它们的和之差。文章通过分析指出,简单的枚举方法会导致时间复杂度爆炸,进而提出使用折半枚举结合二分查找的方法,降低了复杂度。具体实现中,通过从前面的数中选择一部分,然后在后面的数中找到最接近目标和的组合,从而达到最小化差值的目标。

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

题解:只按照假设有30个数的情况来算

如果单纯直接枚举的话,那么就是 30 ∗ 29 ∗ 28 ∗ ⋯ ∗ 15 30 * 29 * 28 * \dots * 15 30292815,这样的话时间复杂度直接爆炸。

那么考虑折半枚举,考虑从前面15个数中选出了 i i i个数,那么必然就得从后面15个数中选出 j = 15 − i j=15-i j=15i个数,这样复杂度是
∑ i = 0 15 C 15 i ∗ C 15 15 − i {\textstyle \sum_{i=0}^{15}} C_{15}^{i}*C_{15}^{15-i} i=015C15iC1515i
这样也是会T掉的,但是考虑一个情况是这样的,对于一个数 s u m sum sum,并且 a + b = s u m a+b=sum a+b=sum,现在想让 ∣ a − b ∣ |a-b| ab达到最小值,最直观的想法就是 a a a b b b相等,不能相等也就尽量让 a 、 b a、b ab中较小的那个数往 s u m / 2 sum/2 sum/2上凑,也就是说我们从前面15个数中选出了 i i i个数的和之后,然后直接二分从后面15个数中选出 j = 15 − i j=15-i j=15i个数中最接近($sum/2 - 前 面 15 个 数 中 选 出 了 前面15个数中选出了 15i$个数的和),使得时间复杂度降低到:
∑ i = 0 15 C 15 i ∗ 3 {\textstyle \sum_{i=0}^{15}} C_{15}^{i}*3 i=015C15i3

#include <bits/stdc++.h>
using namespace std;
#define db double
#define ll long long
#define pii pair<int,int>
#define rush() int T; cin >> T; while (T--)
const int N = 1e5 + 5;
const int M = (N << 2);
const int P = 1e9 + 7;
class Solution {
public:
    int n;
    void init(vector<vector<int> > &now, vector<int> &nums)
    {
        for (int i = 0; i < (1 << n); i ++) {
            int dig = __builtin_popcount(i), sum = 0;
            for (int j = 0; j < n; j ++) if (i >> j & 1) sum += nums[j];
            now[dig].emplace_back(sum);
        }
    }
    int minimumDifference(vector<int>& nums)
    {
        n = nums.size() / 2;
        vector<int> p_nums, s_nums;
        int p_sum, s_sum, sum = 0;
        for (int i = 0; i < nums.size(); i ++) {
            if (i < n) p_nums.emplace_back(nums[i]), p_sum += nums[i];
            else s_nums.emplace_back(nums[i]), s_sum += nums[i];
            sum += nums[i];
        }
        int mid = sum / 2;
        vector<vector<int> > pre(n + 1), suf(n + 1);
        init(pre, p_nums);
        init(suf, s_nums);
        int ans = 0x3f3f3f3f;
        for (int i = 0; i <= n; i ++) {
            int j = n - i;
            sort(suf[j].begin(), suf[j].end());
            for (auto &x : pre[i]) {
                int idx = min((int)suf[j].size() - 1, (int)(lower_bound(suf[j].begin(), suf[j].end(), mid - x) - suf[j].begin()));
                for (int k = -1; k < 2; k ++) {
                    if (idx + k >= 0 && idx + k < suf[j].size()) ans = min(ans, abs(2 * x + 2 * suf[j][idx + k] - sum));
                }
            }
        }
        return ans;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值