2022-11-14 LeetCode 805. 数组的均值分割

传送门:805. 数组的均值分割 – 困难

题目:

给定你一个整数数组 nums

我们要将 nums 数组中的每个元素移动到 A 数组 或者 B 数组中,
使得 A 数组和 B 数组不为空,并且 average(A) == average(B) 。

如果可以完成则返回true , 否则返回 false  。

注意:对于数组 arr ,  average(arr) 是 arr 的所有元素的和除以 arr 长度。

输入输出示例:

示例 1:

输入: nums = [1,2,3,4,5,6,7,8]
输出: true
解释: 我们可以将数组分割为 [1,4,5,8] 和 [2,3,6,7], 他们的平均值都是4.5。
----------------------------------------------------------------
示例 2:

输入: nums = [3,1]
输出: false

数据范围:

1 < = n u m s . l e n g t h < = 30 1 <= nums.length <= 30 1<=nums.length<=30
0 < = n u m s [ i ] < = 1 0 4 0 <= nums[i] <= 10^4 0<=nums[i]<=104

解题思路:

记初始数组的和为 s u m sum sum ,个数为 n n n,分成的第一个数组为 A r r a y 1 Array_1 Array1,第二个数组为 A r r a y 2 Array_2 Array2

暴力想法:

枚举 S u m ( A r r a y 1 ) Sum(Array_1) Sum(Array1) i i i A r r a y 1 Array_1 Array1 数组的个数 为 j j j,此时只需要判断下列式子成立即可。
i j = s u m − i n − j 转 化 为 i ∗ ( n − j ) = ( s u m − i ) ∗ j      ( 1 ) \frac{i}{j}=\frac{sum-i}{n-j}\\ 转化为\\ i * (n - j) = (sum - i) * j\ \ \ \ (1) ji=njsumii(nj)=(sumi)j    (1)
那么问题就在于如何求解从 n n n 个数中取出 j j j 数使其之和为 i i i,要枚举 C n j C_n^j Cnj 种方案。

此时时间复杂度为 O ( s u m ∗ n ∗ 2 n ) O(sum * n * 2^n) O(sumn2n),复杂度太高难以承受。

优化思路:

观察 ( 1 ) (1) (1)式,发现可以有优化之处,对于选定的 j j j 值,可以推导出 i i i 值,化简可得:
i = s u m ∗ j n      ( 2 ) i = \frac{sum * j}{n}\ \ \ \ (2) i=nsumj    (2)
对于求解从 n n n 个数中取出 j j j 数使其之和为 i i i 的问题,发现题目规定的数组大小 n n n 仅在 30 30 30 以内,因此可以将原始数组平均分为左右两个子数组,记为 v e 1 ve1 ve1 v e 2 ve2 ve2,然后分别进行预处理出所有可能的分配结果。

因此只需要枚举 j j j, 以及左子数组 v e 1 ve1 ve1 的个数 k k k ,并做对应的判断即可极大降低复杂度,得出正解。

具体代码如下,附加注释。

AC代码:

class Solution {
public:
    
    bool splitArraySameAverage(vector<int>& nums) {
        vector<int>ve1[20];
        vector<int>ve2[20];
        int sum = 0,n = nums.size();
        for(int i = 0;i < n;i ++)sum += nums[i];

        ve1[0].push_back(0);
        ve2[0].push_back(0);
        
        ///将原始数组分为左右两部分 分别计算从n中取出m个数可能的情况
        int bit1 = 1 << (n / 2);
        for(int i = 1;i < bit1;i ++){
            int x = i,vis = 0,index = 0,res = 0;
            while(x){
                if(x & 1){
                    vis ++;
                    res += nums[index];
                }
                x >>= 1;
                index ++;
            }
            ve1[vis].push_back(res);
        }

        int bit2 = 1 << (n - n / 2);
        for(int i = 1;i < bit2;i ++){
            int x = i,vis = 0,index = 0,res = 0;
            while(x){
                if(x & 1){
                    vis ++;
                    res += nums[n / 2 + index];
                }
                x >>= 1;
                index ++;
            }
            ve2[vis].push_back(res);
        }

        ///分别进行sort排序 方便后续二分查找
        for(int i = 1;i <= n / 2;i ++)sort(ve1[i].begin(),ve1[i].end());
        for(int i = 1;i <= n - n / 2;i ++)sort(ve2[i].begin(),ve2[i].end());

        ///枚举第一个数组应该有的个数
        for(int j = 1;j <= n / 2;j ++){
            ///如果第一个数组的和是为整数 说明可以分为两个数组
            if(sum * j % n == 0){
               	///计算出第一个数组的和
                int i = sum * j / n;
                ///枚举在左子数组的个数
                for(int k = 0;k <= j;k ++){
                    ///枚举在左子数组的数据里的和
                    for(int m = 0;m < ve1[k].size();m ++){
                        if(i - ve1[k][m] >= 0){
                            ///右子数组的数据里要取pos个数 而且和要等于i - ve1[k][m]
                            int dis = i - ve1[k][m],pos = j - k;

                            ///如果剩下的数小于n - n / 2才可以在右边的数组中找到
                            if(pos <= n - n / 2){
                                ///二分查找
                                int res = lower_bound(ve2[pos].begin(),ve2[pos].end(),dis) - ve2[pos].begin();
                                if(res < ve2[pos].size() && ve2[pos][res] == dis)return true;

                            }else{
                                ///否则只能是在左子数组里满足要求
                                if(dis == 0)return true;
                            }
                        }
                    }    
                }
            }
        }
        return false;
    }
};

执行用时: 136 ms

内存消耗: 16.7 MB

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值