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. 第十五节: EF的CodeFirst模式通过DataAnnotations修改默认协定

    一. 简介 1. DataAnnotations说明:EF提供以特性的方式添加到 domain classes上,其中包括两类:  A:System.ComponentModel.DataAnnota ...

  2. ArcGis Classic COM Add-Ins插件开发的一般流程 C#

    COM add-ins是我对这种开发方式的称呼,Esri的官方文档里称其为“Extending ArcObject”或者“Classic COM extensibility”,Esri所称的addin ...

  3. DQL用户、权限管理(mysql8.0)

    DQL用户.权限管理(mysql8.0) 查看所有的用户: use mysql; -- 使用mysql数据库 select * from user; -- 查询user表中的全部信息,也就是用户 创建 ...

  4. [物理学与PDEs]第3章第2节 磁流体力学方程组 2.1 考虑到导电媒质 (等离子体) 的运动对 Maxwell 方程组的修正

    1.  Maxwell 方程组 $$\bee\label{3_2_1_Maxwell} \bea \Div{\bf D}&=\rho_f,\\ \rot{\bf E}&=-\cfrac ...

  5. 练习:javascript-setInterval动画运动平移,定时器动画练习

    常见问题:定时器加速问题,每次点击会启动一个定时器,解决先清除定时器 <!DOCTYPE html> <html lang="en"> <head&g ...

  6. php session的用法

    初始 Session: session_start(). 存储 Session 变量:$_SESSION['name']='快聘100'; 调用Session:$name = $_SESSION['n ...

  7. SQL Server - group by

    转载自https://segmentfault.com/a/1190000006821331 在平时的开发任务中我们经常会用到MYSQL的GROUP BY分组, 用来获取数据表中以分组字段为依据的统计 ...

  8. pt-online-schema-change VS oak-online-alter-table【转】

    前言 在上篇文章中提到了MySQL 5.6 Online DDL,如果是MySQL 5.5的版本在DDL方面是要付出代价的,虽然已经有了Fast index Creation,但是在添加字段还是会锁表 ...

  9. 【原创】大叔经验分享(2)为什么hive在大表上加条件后执行limit很慢

    问题重现 select id from big_table where name = 'sdlkfjalksdjfla' limit 100; 首先看执行计划: hive> explain se ...

  10. Redis数据类型String

    set和get Set带空格的字符串值,这时候就需要使用双引号了,否则会出错 getset getset设置某个key-value,然后把设置之前的值返回来 incr,incrby 和 decr,de ...