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]就无法满足条件。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
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 spli
