给定你一个整数数组 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;
}
};