题目:
给定你一个整数数组 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=n−jsum−i转化为i∗(n−j)=(sum−i)∗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(sum∗n∗2n),复杂度太高难以承受。
优化思路:
观察
(
1
)
(1)
(1)式,发现可以有优化之处,对于选定的
j
j
j 值,可以推导出
i
i
i 值,化简可得:
i
=
s
u
m
∗
j
n
(
2
)
i = \frac{sum * j}{n}\ \ \ \ (2)
i=nsum∗j (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