原文链接: https://leetcode-cn.com/problems/split-array-with-same-average
英文原文
You are given an integer array nums
.
You should move each element of nums
into one of the two arrays A
and B
such that A
and B
are non-empty, and average(A) == average(B)
.
Return true
if it is possible to achieve that and false
otherwise.
Note that for an array arr
, average(arr)
is the sum of all the elements of arr
over the length of arr
.
Example 1:
Input: nums = [1,2,3,4,5,6,7,8] Output: true Explanation: We can split the array into [1,4,5,8] and [2,3,6,7], and both of them have an average of 4.5.
Example 2:
Input: nums = [3,1] Output: false
Constraints:
1 <= nums.length <= 30
0 <= nums[i] <= 104
中文题目
给定的整数数组 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]
.
通过代码
官方题解
方法一:折半搜索
假设我们在数组 B
中放了 K
个数,数组 C
中放了 N - K
个数,它们的平均值相等,即 sum(B) / K = sum(C) / (N - K)
,那么有
sum(B) * (N - K) = sum(C) * K
==> sum(B) * N = (sum(B) + sum(C)) * K
==> sum(B) / K = (sum(B) + sum(C)) / N
==> sum(B) / K = sum(A) / N
即 B
的平均值与 A
的平均值相等。因此我们可以将 A
中的每个元素减去它们的平均值,这样 A
的平均值变为 0
。此时我们的问题变成:找出若干个元素组成集合 B
,这些元素的和为 0
。
一个容易想到的思路是,N
个元素中取出若干个有 2^N
种方法(即每个元素取或不取),我们依次判断每种方法选出的元素之和是否为 0
。但题目中的 N
可以达到 30
,2^N
会非常大。因此我们考虑将数组平均分成两部分 A0
和 A1
,它们均含有 N/2
个元素(不失一般性,这里假设 N
为偶数。如果 N
为奇数,在 A0
中多放一个元素即可),此时这两个数组分别有 2^(N/2)
种选择的方法。设 A0
中所有选择的方法得到的元素之和的集合为 left
,A1
中所有选择的方法得到的元素之和的集合为 right
,那么我们只需要在 left
中找出一个 x
,使得 right
中包含 -x
,那么就找到了一种和为 0
的方法。需要注意的是,我们不能同时选择 A0
和 A1
中的所有元素,这样 C
就为空了。
此外,“将 A
中每个元素减去它们的平均值” 这一步会引入浮点数,可能会导致判断的时候出现误差。一种解决方案是使用分数代替浮点数,但这样代码编写起来较为麻烦。更好的解决方案是先将 A
中的每个元素乘以 A
的长度,这样它们的平均值一定为整数。
import java.awt.Point;
class Solution {
public boolean splitArraySameAverage(int[] A) {
int N = A.length;
int S = 0;
for (int x: A) S += x;
if (N == 1) return false;
int g = gcd(S, N);
Point mu = new Point(-(S/g), N/g);
// A[i] -> fracAdd(A[i], mu)
List<Point> A2 = new ArrayList();
for (int x: A)
A2.add(fracAdd(new Point(x, 1), mu));
Set<Point> left = new HashSet();
left.add(A2.get(0));
for (int i = 1; i < N/2; ++i) {
Set<Point> left2 = new HashSet();
Point z = A2.get(i);
left2.add(z);
for (Point p: left) {
left2.add(p);
left2.add(fracAdd(p, z));
}
left = left2;
}
if (left.contains(new Point(0, 1))) return true;
Set<Point> right = new HashSet();
right.add(A2.get(N-1));
for (int i = N/2; i < N-1; ++i) {
Set<Point> right2 = new HashSet();
Point z = A2.get(i);
right2.add(z);
for (Point p: right) {
right2.add(p);
right2.add(fracAdd(p, z));
}
right = right2;
}
if (right.contains(new Point(0, 1))) return true;
Point sleft = new Point(0, 1);
for (int i = 0; i < N/2; ++i)
sleft = fracAdd(sleft, A2.get(i));
Point sright = new Point(0, 1);
for (int i = N/2; i < N; ++i)
sright = fracAdd(sright, A2.get(i));
for (Point ha: left) {
Point ha2 = new Point(-ha.x, ha.y);
if (right.contains(ha2) && (!ha.equals(sleft) || !ha2.equals(sright)))
return true;
}
return false;
}
public Point fracAdd(Point A, Point B) {
int numer = A.x * B.y + B.x * A.y;
int denom = A.y * B.y;
int g = gcd(numer, denom);
numer /= g;
denom /= g;
if (denom < 0) {
numer *= -1;
denom *= -1;
}
return new Point(numer, denom);
}
public int gcd(int a, int b) {
if (b==0) return a;
return gcd(b, a%b);
}
}
class Solution(object):
def splitArraySameAverage(self, A):
from fractions import Fraction
N = len(A)
S = sum(A)
A = [z - Fraction(S, N) for z in A]
if N == 1: return False
#Want zero subset sum
left = {A[0]}
for i in xrange(1, N/2):
left = {z + A[i] for z in left} | left | {A[i]}
if 0 in left: return True
right = {A[-1]}
for i in xrange(N/2, N-1):
right = {z + A[i] for z in right} | right | {A[i]}
if 0 in right: return True
sleft = sum(A[i] for i in xrange(N/2))
sright = sum(A[i] for i in xrange(N/2, N))
return any(-ha in right and (ha, -ha) != (sleft, sright) for ha in left)
复杂度分析
时间复杂度:$O(2^{N/2})$,其中 $N$ 是数组
A
的长度。空间复杂度:$O(2^{N/2})$。
统计信息
通过次数 | 提交次数 | AC比率 |
---|---|---|
2858 | 9704 | 29.5% |
提交历史
提交时间 | 提交结果 | 执行时间 | 内存消耗 | 语言 |
---|