1. 问题背景与核心挑战
今天遇到一道有趣的LeetCode题目(编号805),要求判断是否能把一个非空数组分成两个非空子集,使得两个子集的平均值相等。初看题目似乎简单,但深入思考后发现其中暗藏不少精妙之处。
这道题归类在"动态规划"和"数学"标签下,实际考察的是对问题本质的抽象能力。举个例子,给定数组[1,2,3,4,5,6,7,8],我们可以分成[1,4,5,8]和[2,3,6,7],两者的平均值都是4.5。但如果是[1,2,3,4]就无法满足条件。
2. 数学原理与问题转化
2.1 关键数学推导
问题的核心在于数学转化。设原数组总和为S,元素个数为n。若存在分割,设其中一个子集A有k个元素,和为S_A,则必须满足:
S_A/k = (S - S_A)/(n - k)
化简后得到:S_A = k*S/n
这意味着我们需要找到子集A,使得其元素个数k与总和S_A满足这个线性关系。这里就引出了第一个重要观察:k*S必须能被n整除,否则直接返回false。
2.2 可行性剪枝
基于上述推导,我们可以预先做快速判断:
- 计算数组总和S
- 遍历可能的k值(1到n/2)
- 检查k*S是否能被n整除
如果没有任何k满足条件,直接返回false。这一步可以过滤掉约50%的无效用例。
3. 动态规划解法实现
3.1 状态设计
采用动态规划来解决这个背包问题的变种。定义dp[i][j]表示使用前i个元素,能否选出j个元素使其和为j*S/n。最终答案就是存在某个k使得dp[n][k]为true。
由于n可能很大(题目限制30),直接二维DP会有空间问题。这里采用滚动数组优化,使用一维的HashSet来记录可能达到的(sum, count)组合。
3.2 算法步骤
- 计算数组总和S和长度n
- 检查是否存在k满足k*S%n == 0
- 初始化DP集合,包含(0,0)
- 遍历每个数字num:
- 对于集合中每个(sum, count),生成新的(sum+num, count+1)
- 检查是否满足sum+num = (count+1)*S/n
- 如果找到满足条件的组合则返回true
3.3 代码实现
java复制public boolean splitArraySameAverage(int[] nums) {
int n = nums.length, S = Arrays.stream(nums).sum();
boolean possible = false;
// 检查是否存在可行的k
for (int k = 1; k <= n/2; ++k) {
if (S*k % n == 0) {
possible = true;
break;
}
}
if (!possible) return false;
// DP初始化
Set<Pair<Integer, Integer>> dp = new HashSet<>();
dp.add(new Pair(0, 0));
for (int num : nums) {
Set<Pair<Integer, Integer>> temp = new HashSet<>();
for (Pair<Integer, Integer> p : dp) {
int sum = p.getKey() + num;
int count = p.getValue() + 1;
if (sum * n == S * count && count < n) {
return true;
}
temp.add(new Pair(sum, count));
}
dp.addAll(temp);
}
return false;
}
4. 优化技巧与注意事项
4.1 性能优化点
- 提前终止:一旦找到满足条件的组合立即返回,避免不必要的计算
- 剪枝策略:检查k的可行性时,只需遍历到n/2即可(对称性)
- 去重处理:使用Set避免重复计算相同的(sum, count)组合
4.2 边界情况处理
- 数组长度为1时直接返回false
- 所有元素相同的情况需要特殊处理
- 整数除法可能导致精度问题,应该用乘法形式判断相等
4.3 复杂度分析
时间复杂度:O(n^2 * S),其中S是数组总和。由于题目限制n≤30,S≤10^4,这个复杂度是可接受的。
空间复杂度:O(n * S),主要来自DP集合的存储。
5. 测试用例设计
验证算法时需要覆盖以下典型场景:
- 普通可分割案例:[1,2,3,4,5,6,7,8]
- 不可分割案例:[1,2,3,4]
- 全相同元素:[3,3,3,3]
- 含零元素:[0,0,0,0]
- 大数案例:[1000,2000,3000,4000]
- 边界案例:[1](单元素)
6. 算法扩展思考
这个问题可以延伸到以下方向:
- 如果允许分成k个子集(k>2),如何判断?
- 如果数组包含浮点数,如何处理精度问题?
- 如果需要返回具体分割方案而不仅是判断,如何修改算法?
在实际工程中,类似的均值分割问题可能出现在负载均衡、资源分配等场景。理解这个问题的解法有助于处理更复杂的分配问题。