给定的整数数组 A ,我们要将 A数组 中的每个元素移动到 B数组 或者 C数组中。(B数组和C数组在开始的时候都为空)
返回true ,当且仅当在我们的完成这样的移动后,可使得B数组的平均值和C数组的平均值相等,并且B数组和C数组都不为空。
示例:
输入:
[1,2,3,4,5,6,7,8]
输出: true
解释: 我们可以将数组分割为 [1,4,5,8] 和 [2,3,6,7], 他们的平均值都是4.5。
注意:
A 数组的长度范围为 [1, 30].
A[i] 的数据范围为 [0, 10000].
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/split-array-with-same-average
解法:
折半搜索,状态压缩,二分查找/双指针。由题意可设数组B中有K个元素,那么
即数组B的平均值等于数组A的平均值。我们可以先对数组进行预处理,数组A中的元素先减去数组A的平均值。那么就转化为求数组A的子序列中求和为0的子序列。求子序列的问题,根据A 数组的长度范围为 [1, 30],可以知道用折半查找+状态压缩。
将数组A分为前后两半,每一半利用状态压缩求和。此时答案可能出现的地方为:
1)前半部分中有子集的和为0;
2)后半部分中有子集的和为0;
3)前半部分某一子集+后半部分某一子集的和为0;
我们将状态压缩中为数位为1的B数组中的数。此题还有许多细节问题:如B数组和C数组都不为空,那么在判断第三种情况时,要是有解的话,前半部分不能全不贡献也不能为全贡献,全不贡献的话,代表后半部分必有子集为0,满足条件2。全贡献的话同样代表后半部分必有子集为0(属于数组C的部分)。后半部分同理。
class Solution {
const double eps = 1e-2;
public:
bool splitArraySameAverage(vector<int>& nums) {
int n = nums.size();
if (n == 1 || (n == 2 && nums[0] != nums[1]))return false;
int total = 0;
for (auto &num : nums)
total += num;
vector<double>dnums;
for (auto &num : nums)
dnums.push_back(num - total*1.0 / n);
int len1 = n / 2, len2 = n - len1;
auto getSum = [&](int x, int start, int len){
double sum = 0;
//int cnt = 0;
for (int i = 0; i < len; ++i)
{
if (x & 1 << i)
{
sum += dnums[start + i];
//++cnt;
}
}
return sum;
};
vector<double> v1;
for (int mask = 1; mask <(1 << len1); ++mask) //从1开始
{
double sum = getSum(mask, 0, len1);
if (fabs(sum)<=eps)
return true;
v1.push_back(sum);
}
v1.pop_back(); //移除全贡献
sort(v1.begin(), v1.end());
//double target=total*1.0/n;
vector<double> v2;
for (int mask = 1; mask <(1 << len2); ++mask) //同理
{
double sum = getSum(mask, len1, len2);
if (fabs(sum)<=eps)
return true;
v2.push_back(sum);
}
v2.pop_back();
sort(v2.begin(), v2.end());
int vlen1=v1.size(), vlen2=v2.size();
//cout<<v1<<" "<<
int id1=0, id2=vlen2-1; //双指针:在两个有序数组中寻找目标值
while(id1<vlen1&&id2>=0)
{
double temp=v1[id1]+v2[id2]; //注意double
if(fabs(temp)<=eps)
return true;
else if(temp>0)
id2--;
else
id1++;
}
return false;
}
};