In a given integer array A, we must move every element of A to either list B or list C. (B and C initially start empty.)

Return true if and only if after such a move, it is possible that the average value of B is equal to the average value of C, and B and C are both non-empty.

Example :
Input:
[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 the average of 4.5.

Note:

  • The length of A will be in the range [1, 30].
  • A[i] will be in the range of [0, 10000].

这道题给了我们一个数组A,问是否可以把数组分割成两个小数组,并且要求分成的这两个数组的平均值相同。之前我们有做过分成和相同的两个小数组 Split Array with Equal Sum,看了题目中的给的例子,你可能会有种错觉,觉得这两个问题是一样的,因为题目中分成的两个小数组的长度是一样的,那么平均值相同的话,和一定也是相同的。但其实是不对的,很简单的一个例子,比如数组 [2, 2, 2],可以分成平均值相同的两个数组 [2, 2] 和 [2],但是无法分成和相同的两个数组。 现在唯一知道的就是这两个数组的平均值相等,这里有个隐含条件,就是整个数组的平均值也和这两个数组的平均值相等,这个不用多说了吧,加法的结合律的应用啊。由于平均值是由数字总和除以个数得来的,那么假设整个数组有n个数组,数字总和为 sum,分成的其中一个数组有k个,假设其数字和为 sum1,那么另一个数组就有 n-k 个,假设其数组和为 sum2,就有如下等式:

sum / n = sum1 / k = sum2 / (n - k)

看前两个等式,sum / n = sum1 / k,可以变个形,sum * k / n = sum1,那么由于数字都是整数,所以 sum * k 一定可以整除 n,这个可能当作一个快速的判断条件。下面来考虑k的取值范围,由于要分成两个数组,可以始终将k当作其中较短的那个数组,则k的取值范围就是 [1, n/2],就是说,如果在这个范围内的k,没有满足的 sum * k % n == 0 的话,那么可以直接返回false,这是一个快速的剪枝过程。如果有的话,也不能立即说可以分成满足题意的两个小数组,最简单的例子,比如数组 [1, 3],当k=1时,sum * k % n == 0 成立,但明显不能分成两个平均值相等的数组。所以还需要进一步检测,当找到满足的 sum * k % n == 0 的k了时候,其实可以直接算出 sum1,通过 sum * k / n,那么就知道较短的数组的数字之和,只要能在原数组中数组找到任意k个数字,使其和为 sum1,就可以 split 成功了。问题到这里就转化为了如何在数组中找到任意k个数字,使其和为一个给定值。有点像 Combination Sum III 那道题,当然可以根据不同的k值,都分别找原数组中找一遍,但想更高效一点,因为毕竟k的范围是固定的,可以事先任意选数组中k个数字,将其所有可能出现的数字和保存下来,最后再查找。那么为了去重复跟快速查找,可以使用 HashSet 来保存数字和,可以建立 n/2 + 1 个 HashSet,多加1是为了不做数组下标的转换,并且防止越界,因为在累加的过程中,计算k的时候,需要用到 k-1 的情况。讲到这里,你会不会一拍大腿,吼道,这尼玛不就是动态规划 Dynamic Programming 么。恭喜你骚年,没错,这里的 dp 数组就是一个包含 HashSet 的数组,其中 dp[i] 表示数组中任选 i 个数字,所有可能的数字和。首先在 dp[0] 中加入一个0,这个是为了防止越界。更新 dp[i] 的思路是,对于 dp[i-1] 中的每个数字,都加上一个新的数字,所以最外层的 for 循环是遍历原数组的中的每个数字的,中间的 for 循环是遍历k的,从 n/2 遍历到1,然后最内层的 for 循环是遍历 dp[i-1] 中的所有数组,加上最外层遍历到的数字,并存入 dp[i] 即可。整个 dp 数组更新好了之后,下面就是验证的环节了,对于每个k值,验证若 sum * k / n == 0 成立,并且 sum * i / n 在 dp[i] 中存在,则返回 true。最后都没有成立的话,返回 false,参见代码如下:

解法一:

class Solution {
public:
bool splitArraySameAverage(vector<int>& A) {
int n = A.size(), m = n / , sum = accumulate(A.begin(), A.end(), );
bool possible = false;
for (int i = ; i <= m && !possible; ++i) {
if (sum * i % n == ) possible = true;
}
if (!possible) return false;
vector<unordered_set<int>> dp(m + );
dp[].insert();
for (int num : A) {
for (int i = m; i >= ; --i) {
for (auto a : dp[i - ]) {
dp[i].insert(a + num);
}
}
}
for (int i = ; i <= m; ++i) {
if (sum * i % n == && dp[i].count(sum * i / n)) return true;
}
return false;
}
};

下面这种解法跟上面的解法十分的相似,唯一的不同就是使用了 bitset 这个数据结构,在之前那道 Partition Equal Subset Sum 的解法二中,也使用了 biset,了解了其使用方法后,就会发现使用这里使用它只是单纯的为了炫技而已。由于 biset 不能动态变换大小,所以初始化的时候就要确定,题目中限定了数组中最多 30 个数字,每个数字最大 10000,那么就初始化 n/2+1 个 biset,每个大小为 300001 即可。然后每个都初始化个1进去,之后更新的操作,就是把 bits[i-1] 左移 num 个,然后或到 bits[i] 即可,最后查找的时候,有点像二维数组的查找方式一样,直接两个中括号坐标定位即可,参见代码如下:

解法二:

class Solution {
public:
bool splitArraySameAverage(vector<int>& A) {
int n = A.size(), m = n / , sum = accumulate(A.begin(), A.end(), );
bool possible = false;
for (int i = ; i <= m && !possible; ++i) {
if (sum * i % n == ) possible = true;
}
if (!possible) return false;
bitset<> bits[m + ] = {};
for (int num : A) {
for (int i = m; i >= ; --i) {
bits[i] |= bits[i - ] << num;
}
}
for (int i = ; i <= m; ++i) {
if (sum * i % n == && bits[i][sum * i / n]) return true;
}
return false;
}
};

再来看一种递归的写法,说实话在博主看来,一般不使用记忆数组的递归解法,等同于暴力破解,基本很难通过 OJ,除非你进行了大量的剪枝优化处理。这里就是这种情况,首先还是常规的k值快速扫描一遍,确保可能存在解。然后给数组排了序,然后对于满足 sum * k % n == 0 的k值,进行了递归函数的进一步检测。需要传入当前剩余数字和,剩余个数,以及在原数组中的遍历位置,如果当前数字剩余个数为0了,说明已经取完了k个数字了,那么如果剩余数字和为0了,则说明成功的找到了k个和为 sum * k / n 的数字,返回 ture,否则 false。然后看若当前要加入的数字大于当前的平均值,则直接返回 false,因为已经给原数组排过序了,之后的数字只会越来越大,一旦超过了平均值,就不可能再降下来了,这是一个相当重要的剪枝,估计能过 OJ 全靠它。之后开始从 start 开始遍历,当前遍历的结束位置是原数组长度n减去当前剩余的数字,再加1,因为确保给 curNum 留够足够的位置来遍历。之后就是跳过重复,对于重复的数字,只检查一遍就好了。调用递归函数,此时的 curSum 要减去当前数字 A[i],curNum 要减1,start 为 i+1,若递归函数返回 true,则整个返回 true。for 循环退出后返回 false。令博主感到惊讶的是,这个代码的运行速度比之前的DP解法还要快,叼,参见代码如下:

解法三:

class Solution {
public:
bool splitArraySameAverage(vector<int>& A) {
int n = A.size(), m = n / , sum = accumulate(A.begin(), A.end(), );
bool possible = false;
for (int i = ; i <= m && !possible; ++i) {
if (sum * i % n == ) possible = true;
}
if (!possible) return false;
sort(A.begin(), A.end());
for (int i = ; i <= m; ++i) {
if (sum * i % n == && helper(A, sum * i / n, i, )) return true;
}
return false;
}
bool helper(vector<int>& A, int curSum, int curNum, int start) {
if (curNum == ) return curSum == ;
if (A[start] > curSum / curNum) return false;
for (int i = start; i < A.size() - curNum + ; ++i) {
if (i > start && A[i] == A[i - ]) continue;
if(helper(A, curSum - A[i], curNum - , i + )) return true;
}
return false;
}
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/805

类似题目:

Combination Sum III

Partition Equal Subset Sum

Split Array with Equal Sum

参考资料:

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

https://leetcode.com/problems/split-array-with-same-average/discuss/120660/Java-accepted-recursive-solution-with-explanation

https://leetcode.com/problems/split-array-with-same-average/discuss/120842/DP-with-bitset-over-*sum*-(fast-PythonRuby-decent-C%2B%2B)

https://leetcode.com/problems/split-array-with-same-average/discuss/120667/C%2B%2B-Solution-with-explanation-early-termination-(Updated-for-new-test-case)

LeetCode All in One 题目讲解汇总(持续更新中...)

[LeetCode] Split Array With Same Average 分割数组成相同平均值的小数组的更多相关文章

  1. [LeetCode] Split Array with Equal Sum 分割数组成和相同的子数组

    Given an array with n integers, you need to find if there are triplets (i, j, k) which satisfies fol ...

  2. [LeetCode] Split Array into Fibonacci Sequence 分割数组成斐波那契序列

    Given a string S of digits, such as S = "123456579", we can split it into a Fibonacci-like ...

  3. [Swift]LeetCode805. 数组的均值分割 | Split Array With Same Average

    In a given integer array A, we must move every element of A to either list B or list C. (B and C ini ...

  4. [LeetCode] 805. Split Array With Same Average 用相同均值拆分数组

    In a given integer array A, we must move every element of A to either list B or list C. (B and C ini ...

  5. [LeetCode] Split Linked List in Parts 拆分链表成部分

    Given a (singly) linked list with head node root, write a function to split the linked list into k c ...

  6. [LeetCode] Split Array Largest Sum 分割数组的最大值

    Given an array which consists of non-negative integers and an integer m, you can split the array int ...

  7. [LeetCode] Split Array into Consecutive Subsequences 将数组分割成连续子序列

    You are given an integer array sorted in ascending order (may contain duplicates), you need to split ...

  8. [LeetCode] 548. Split Array with Equal Sum 分割数组成和相同的子数组

    Given an array with n integers, you need to find if there are triplets (i, j, k) which satisfies fol ...

  9. Leetcode: Split Array Largest Sum

    Given an array which consists of non-negative integers and an integer m, you can split the array int ...

随机推荐

  1. API.day01

    第1部分 JDK API 1.1 API(Application Programming Interface,应用接口程序):已经封装好可以直接调用的功能(Java中以类的形式封装) 经常使用的JDK ...

  2. 数据库的URL格式

    Oracle数据库: 驱动jar包: ojdbc6.jar 驱动程序类名字:oracle.jdbc.OracleDriver JDBC URL:jdbc:oracle:thin:@//<host ...

  3. 第29月第18天 mac evpp环境

    1.boost https://github.com/Orphis/boost-cmake/ 2.evpp brew install libevent brew install glog /usr/l ...

  4. django上下文处理器的基本使用

    1.定义一个方法 2.在django里面的settings.py里面修改配置文件 3.最后在模板里面调用 操做步骤如下: 这是在settings.py里面配置的文件   在模板里面调用上下文处理器

  5. Ubuntu 16.04 总出现红色圆圈警告和检测到系统程序出现问题

    这种问题不可忽视!不可忽视!不可忽视!重要的事情说三遍!!!(一次死机,好多文件丢失,真是痛苦的经历) 自从从第三方安装了Python3.6,并将默认3.5改为3.6,导致ubuntu16.04右上角 ...

  6. (四)Java工程化--Git基础

    GIT学习参考:https://git-scm.com/book/zh/v2 常见命令 git init 初始化项目 git add *.java 添加文件到git版本控制(.java后缀的全部文件) ...

  7. Illegal invocation with document.querySelector [duplicate]

    document.querySelectorAll赋给其它变量时, 为什么要.bind(document)? https://stackoverflow.com/questions/12637061/ ...

  8. webpack dev-server 允许移动端调试

    "dev": "cross-env NODE_ENV=development webpack-dev-server --host 0.0.0.0 --open --hot ...

  9. Django目录

    app和ORM的操作与介绍 框架简介 中间件 form介绍 Django自带用户认证 cookie和session 模版 模板2 ORM操作 所有ORM操作(第二版) Django请求生命周期 Dja ...

  10. CentOS安装Supervisor

    什么是Supervisor Supervisor是一个进程控制系统. 它是一个C/S系统,服务端是supervisord进程,控制端使用supervisorctl来进行控制启动进程.同时它也提供了一个 ...