# 0805. 数组的均值分割

### 题目地址(805. 数组的均值分割)

<https://leetcode-cn.com/problems/split-array-with-same-average/>

### 题目描述

```
给定的整数数组 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-solution/thinkings/backtrack.md)

### 公司

* 暂无

### 思路

实际上分出的**两个列表 B 和 C 的均值都等于列表 A 的均值**，这是本题的入手点。以下是证明：

令 B 的长度为 K，A 的长度为 N。 则有 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
```

因此我们可以枚举所有的 A 的大小 i，相应地 B 的大小就是 n - i，其中 n 为数组 A 的大小。

而由于**两个列表 B 和 C 的均值都等于列表 A 的均值**。因此可以提前计算出 A 的均值 avg，那么 A 的总和其实就是 i \* avg ，我们使用回溯找到一个和为 i \* avg 的组合，即可返回 true，否则返回 false。

值得注意的是，我们只需要枚举 i 为 1 到 N//2 范围即可，这可以达到剪枝的效果。

核心代码：

```py
def splitArraySameAverage(self, A: List[int]) -> bool:
        n = len(A)
        avg = sum(A) / n

        for i in range(1, n // 2 + 1):
            for combination in combinations(A, i):
                if abs(sum(combination) - avg * i) < 1e-6:
                    return True
        return False
```

上面代码由于回溯里面嵌套了 sum，因此时间复杂度为**回溯的时间复杂度 \* sum 的时间复杂度**，因此总的时间复杂度在最坏的情况下是 $n \* 2^n$。代入题目的 n 范围是 30，一般这种复杂度只能解决 20 以下的题目，因此需要考虑优化。

我们可以不计算出来所有的组合之后再求和，而是直接计算**所有的和**的组合，这种算法的时间复杂度为 $2^n$。

核心代码：

```py
def splitArraySameAverage(self, A: List[int]) -> bool:
        n = len(A)
        avg = sum(A) / n

        for i in range(1, n // 2 + 1):
            for s in combinationSum(A, i):
                if abs(s - avg * i) < 1e-6:
                    return True
        return False
```

但是遗憾的是，这仍然不足以通过所有的测试用例。

接下来，我们可以通过进一步剪枝的手段来达到 AC 的目的。 很多**回溯**的题目都是基于剪枝来完成的。剪枝是回溯问题的核心考点。

这个技巧就是**双向搜索**，双向搜索相比之前的回溯可达到减少指数数字的效果，从 $O(2^n)$ 降低到 $O(2^(N//2))$。代入题目，这样指数变为了 30/2 = 15，就可以通过了。

具体地，我们可以 combinationSum A 数组的一半（不妨称 A1），然后 combinationSum A 数组的令一半（不妨称 A2），那么 A1 和 A2 的总和如果是 avg \* i 不也行么？简单起见，我们可以令 A1 为数组 A 的前一半， A2 为数组的后一半。

同时，为了避免这种加法，我们可以对问题进行一个转化。即将数组 A 的所有数都减去 avg，这样问题转化为找到一个和为 0 的组合，即可以找到一个和为 avg \* i 的组合。

### 关键点

* 双端搜索

### 代码

* 语言支持：Python3

Python3 Code:

```python
class Solution(object):
    def splitArraySameAverage(self, A):
        from fractions import Fraction
        N = len(A)
        total = sum(A)
        A = [a - Fraction(total, N) for a in A] # 转化后的 A，免于计算 sum

        if N == 1: return False

        S1 = set() # 所有 B 可能的和的集合
        for i in range(N//2):
            # {a + A[i] for a in S1} 在之前选择的基础上选择 A[i] 的新集合
            # {A[i]} 是仅选择 A[i] 的新集合
            # S1 是不选择 A[i] 的集合
            # | 是集合并操作
            S1 = {a + A[i] for a in S1} | S1 | {A[i]}
        if 0 in S1: return True

        S2 = set() # 所有 C 可能的和的集合
        for i in range(N//2, N):
            S2 = {a + A[i] for a in S2} | S2 | {A[i]}
        if 0 in S2: return True
        # 如果 S1 和 S2 都没有和为 0 的组合。那么我们就需要从 S1 和 S2 分别找一个 a 和 b，看其和是否能达到 0. 如果可以，说明也能满足题意
        # 为了避免 B 或者 C 为空，我们增加一个这样的判断： (ha, -ha) != (sleft, sright)
        sleft = sum(A[i] for i in range(N//2))
        sright = sum(A[i] for i in range(N//2, N))

        return any(-ha in S2 and (ha, -ha) != (sleft, sright) for ha in S1)
```

**复杂度分析**

令 n 为数组长度。

* 时间复杂度：$O(2^(N//2))$
* 空间复杂度：$O(2^(N//2))$

> 此题解由 [力扣刷题插件](https://leetcode-pp.github.io/leetcode-cheat/?tab=solution-template) 自动生成。

力扣的小伙伴可以[关注我](https://leetcode-cn.com/u/fe-lucifer/)，这样就会第一时间收到我的动态啦\~

以上就是本文的全部内容了。大家对此有何看法，欢迎给我留言，我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库：<https://github.com/azl397985856/leetcode> 。 目前已经 40K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。

关注公众号力扣加加，努力用清晰直白的语言还原解题思路，并且有大量图解，手把手教你识别套路，高效刷题。

![](https://p.ipic.vip/elnjpp.jpg)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://leetcode-solution-leetcode-pp.gitbook.io/leetcode-solution/hard/805.split-array-with-same-average.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
