|
| 1 | +### 题目描述 |
| 2 | + |
| 3 | +这是 LeetCode 上的 **[805. 数组的均值分割](https://leetcode.cn/problems/split-array-with-same-average/solution/gong-shui-san-xie-by-ac_oier-flsd/)** ,难度为 **困难**。 |
| 4 | + |
| 5 | +Tag : 「折半搜索」、「二进制枚举」、「哈希表」 |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | +给定你一个整数数组 `nums` |
| 10 | + |
| 11 | +我们要将 `nums` 数组中的每个元素移动到 `A` 数组 或者 `B` 数组中,使得 `A` 数组和 `B` 数组不为空,并且 `average(A) == average(B)` 。 |
| 12 | + |
| 13 | +如果可以完成则返回 `true`, 否则返回 `false`。 |
| 14 | + |
| 15 | +注意:对于数组 `arr`, `average(arr)` 是 `arr` 的所有元素除以 `arr` 长度的和。 |
| 16 | + |
| 17 | +示例 1: |
| 18 | +``` |
| 19 | +输入: nums = [1,2,3,4,5,6,7,8] |
| 20 | + |
| 21 | +输出: true |
| 22 | + |
| 23 | +解释: 我们可以将数组分割为 [1,4,5,8] 和 [2,3,6,7], 他们的平均值都是4.5。 |
| 24 | +``` |
| 25 | +示例 2: |
| 26 | +``` |
| 27 | +输入: nums = [3,1] |
| 28 | + |
| 29 | +输出: false |
| 30 | +``` |
| 31 | + |
| 32 | +提示: |
| 33 | +* 1ドル <= nums.length <= 30$ |
| 34 | +* 0ドル <= nums[i] <= 10^4$ |
| 35 | + |
| 36 | +--- |
| 37 | + |
| 38 | +### 折半搜索 + 二进制枚举 + 哈希表 + 数学 |
| 39 | + |
| 40 | +##### 提示一:将长度为 $n,ドル总和为 $sum$ 的原数组划分为两组,使得两数组平均数相同,可推导出该平均数 $avg = \frac{sum}{n}$ |
| 41 | + |
| 42 | +若两数组平均数相同,则由两数组组成的新数组(对应原数组 `nums`)平均数不变,而原数组的平均数可直接算得。 |
| 43 | + |
| 44 | +##### 提示二:原数组长度为 30ドル,ドル直接通过「二进制枚举」的方式来做,计算量为 2ドル^{30},ドル该做法无须额外空间,但会 `TLE`。 |
| 45 | + |
| 46 | +所谓的直接使用「二进制枚举」来做,是指用二进制表示中的 `0` 和 `1` 分别代表在划分数组两边。 |
| 47 | + |
| 48 | +如果直接对原数组进行「二进制枚举」,由于每个 $nums[i]$ 都有两种决策(归属于数组 `A` 或 `B`),共有 2ドル^{30}$ 个状态需要计算。同时每个状态 `state` 而言,需要 $O(n)$ 的时间复杂度来判定,但整个过程只需要有限个变量。 |
| 49 | + |
| 50 | +因此直接使用「二进制枚举」是一个无须额外空间 `TLE` 做法。 |
| 51 | + |
| 52 | +##### 提示三:空间换时间 |
| 53 | + |
| 54 | +我们不可避免需要使用「枚举」的思路,也不可避免对每个 $nums[i]$ 有两种决策。**但我们可以考虑缩减每次搜索的长度,将搜索分多次进行。** |
| 55 | + |
| 56 | +具体的,我们可以先对 `nums` 的前半部分进行搜索,并将搜索记录以「二元组 $(tot, cnt)$ 的形式」进行缓存(`map` 套 `set`),其中 `tot` 为划分元素总和,`cnt` 为划分元素个数;随后再对 `nums` 的后半部分进行搜索,假设当前搜索到结果为 $(tot', cnt'),ドル假设我们能够通过"某种方式"算得另外一半的结果为何值,并能在缓存结果中查得该结果,则说明存在合法划分方案,返回 `true`。 |
| 57 | + |
| 58 | +通过「折半 + 缓存结果」的做法,将「累乘」的计算过程优化成「累加」计算过程。 |
| 59 | + |
| 60 | +##### 提示四:何为"某种方式" |
| 61 | + |
| 62 | +假设我们已经缓存了前半部分的所有搜索结果,并且在搜索后半部分数组时,当前搜索结果为 $(tot', cnt'),ドル应该在缓存结果中搜索何值来确定是否存在合法划分方案。 |
| 63 | + |
| 64 | +假设存在合法方案,且在缓存结果应当被搜索的结果为 $(x, y)$。我们有 $\frac{tot' + x}{cnt' + y} = avg = \frac{sum}{n}$。 |
| 65 | + |
| 66 | +因此我们可以直接枚举系数 $k$ 来进行判定,其中 $k$ 的取值范围为 $[\max(1, cnt'), n - 1],ドル结合上式算得 $t = k \times \frac{sum}{n},ドル若在缓存结果中存在 $(t - tot', k - cnt'),ドル说明存在合法方案。 |
| 67 | + |
| 68 | +代码: |
| 69 | +```Java |
| 70 | +class Solution { |
| 71 | + public boolean splitArraySameAverage(int[] nums) { |
| 72 | + int n = nums.length, m = n / 2, sum = 0; |
| 73 | + for (int x : nums) sum += x; |
| 74 | + Map<Integer, Set<Integer>> map = new HashMap<>(); |
| 75 | + for (int s = 0; s < (1 << m); s++) { |
| 76 | + int tot = 0, cnt = 0; |
| 77 | + for (int i = 0; i < m; i++) { |
| 78 | + if (((s >> i) & 1) == 1) { |
| 79 | + tot += nums[i]; cnt++; |
| 80 | + } |
| 81 | + } |
| 82 | + Set<Integer> set = map.getOrDefault(tot, new HashSet<>()); |
| 83 | + set.add(cnt); |
| 84 | + map.put(tot, set); |
| 85 | + } |
| 86 | + for (int s = 0; s < (1 << (n - m)); s++) { |
| 87 | + int tot = 0, cnt = 0; |
| 88 | + for (int i = 0; i < (n - m); i++) { |
| 89 | + if (((s >> i) & 1) == 1) { |
| 90 | + tot += nums[i + m]; cnt++; |
| 91 | + } |
| 92 | + } |
| 93 | + for (int k = Math.max(1, cnt); k < n; k++) { |
| 94 | + if (k * sum % n != 0) continue; |
| 95 | + int t = k * sum / n; |
| 96 | + if (!map.containsKey(t - tot)) continue; |
| 97 | + if (!map.get(t - tot).contains(k - cnt)) continue; |
| 98 | + return true; |
| 99 | + } |
| 100 | + } |
| 101 | + return false; |
| 102 | + } |
| 103 | +} |
| 104 | +``` |
| 105 | +* 时间复杂度:对原数组前半部分搜索复杂度为 $O(2^{\frac{n}{2}})$;对原数组后半部分搜索复杂度为 $O(2^{\frac{n}{2}}),ドル搜索同时检索前半部分的结果需要枚举系数 `k`,复杂度为 $O(n)$。整体复杂度为 $O(n \times 2^{\frac{n}{2}})$ |
| 106 | +* 空间复杂度:$O(2^{\frac{n}{2}})$ |
| 107 | + |
| 108 | +--- |
| 109 | + |
| 110 | +### 最后 |
| 111 | + |
| 112 | +这是我们「刷穿 LeetCode」系列文章的第 `No.805` 篇,系列开始于 2021年01月01日,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 |
| 113 | + |
| 114 | +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 |
| 115 | + |
| 116 | +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 |
| 117 | + |
| 118 | +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 |
| 119 | + |
0 commit comments