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 <= nums.length <= 30
  • 0 <= nums[i] <= 104

思路: 回溯+折半搜索+数学(力扣官解方法一)

数组的均值分割 - 数组的均值分割 - 力扣(LeetCode)

设数组 nums 的长度为 n,假设我们移动了 k 个元素到数组 A 中,移动了 n−k 个元素到数组 B 中,此时两个数组的平均值相等。设sum(A),sum(B),sum(nums) 分别表示数组 A,B,nums 的元素和,由于数组A,B 的平均值相等,则有 sum(A) / k = sum(B) / (n - k)​
 上式可以进行如下变换:
sum(A)×(n−k)=sum(B)×k
sum(A)×n=(sum(A)+sum(B))×k
sum(A)×n=sum(nums)×k
sum(A)/k=sum(nums)/n

即数组 A 的平均值与数组 nums 的平均值相等。此时我们可以将数组 nums 中的每个元素减去 nums 的平均值,这样数组 nums 的平均值则变为 0。因为数组平均值为0,所以数组之和也为0,故此时题目中的问题则可以变为:从 nums 中找出若干个元素组成集合 A,使得 A 的元素之和为 0,剩下的元素组成集合 B,它们的和也同样为 0

比较容易想到的思路是,从 n 个元素中取出若干个元素形成不同的子集,则此时一共有 2^n
种方法(即每个元素取或不取),我们依次判断每种方法选出的元素之和是否为 0。但题目中的 n 可以达到 30,此时 2^30 =1,073,741,824,组合的数据非常大。

因此我们考虑将数组平均分成两部分 A和 B,它们均含有 n/2个元素,此时这两个数组分别有 2^(n/2)​ 种不同的子集选择的方法,设 A中所有选择的方法得到的不同子集的元素之和的集合为 left,那么若在B中得到了一个子集之和x,则只需在left 中找到一个 -x 即可,此时用A、B的子集构成的集合元素之和就是 0

将数组nums 中每个元素减去它们的平均值,这一步会引入浮点数。所以先将 nums 中的每个元素乘以 nums 的长度 n,则此时每个元素 nums[i] 变为 n×nums[i],这样数组 nums 的平均值就一定为整数。

class Solution {
public:
    unordered_map<int,int> left;//<左半数组k个数的组合之和,k>
    bool backtrack(vector<int>& nums, int start, int end, int sum,int cnt)
    {
        if (start >= end)//越界
            return false;
        if (sum == 0 && cnt != 0)//组合之和为0且组合不为空
            return true;
        //可以在left中找到右半数组的组合之和sum的相反数,两者结合的集合和为0
        //但要注意,两者结合的集合不能把nums中的所有元素都选掉,这样另一个集合就为空了
        if (start >= nums.size() / 2 && left.count(-sum) && left[-sum] + cnt != nums.size())
            return true;
        for (int i = start; i < end; i++)//选择列表
        {
            sum += nums[i];//做选择
            cnt++;
            if(start < nums.size() / 2)//将左半数组k个数的组合之和及k加入left中
                left.emplace(sum,cnt);
            if (backtrack(nums, i + 1, end, sum, cnt))//进入下一层决策树
                return true;
            sum -= nums[i];//取消选择
            cnt--;
        }
        return false;
    }
    bool splitArraySameAverage(vector<int>& nums) {
        int n = nums.size();
        int sum = accumulate(nums.begin(), nums.end(), 0);
        //nums[i]*n使数组平均值也乘n,这样数组平均值就一定不是浮点数
        //nums[i]*n-sum,使数组平均值为0,这样数组之和也为0,只要找到一个子数组之和也为0就行了
        for (int& i : nums)
        {
            i = i * n - sum;
        }
        if (backtrack(nums, 0, n / 2, 0, 0))//左半数组的所有元素的组合之和
            return true;

        if (backtrack(nums, n / 2, n, 0, 0))//右半数组的所有元素之和
            return true;

        return false;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值