1. 题目背景与核心需求解析
今天要拆解的是LeetCode第3637题"三段式数组 I"的第一种解法——单次线性扫描。这道题在最近周赛中频繁出现,属于数组操作类问题的典型代表。题目要求我们判断一个数组是否能被分成三个连续子数组,使得这三个子数组的和相等。
1.1 问题形式化描述
给定一个整数数组nums,我们需要找到两个分割点i和j(其中0 < i < j < n-1,n为数组长度),使得:
- 子数组nums[0...i-1]的和
- 子数组nums[i...j-1]的和
- 子数组nums[j...n-1]的和
三者完全相等。
1.2 暴力解法的局限性
最直观的解法是暴力枚举所有可能的分割点组合,时间复杂度为O(n²)。这在数组长度较大时(比如n=10^5)会完全不可行。因此我们需要寻找更高效的算法。
2. 单次线性扫描算法原理
2.1 前缀和预处理
首先计算数组的前缀和数组prefix,其中prefix[i]表示nums[0]到nums[i-1]的和。这样任意子数组nums[i...j]的和可以快速计算为prefix[j+1] - prefix[i]。
注意:前缀和数组通常会将长度设为n+1,prefix[0]=0,这样能统一处理边界情况。
2.2 关键观察点
假设整个数组的和为total,那么每个子数组的和应该为total/3。因此:
- 首先检查total是否能被3整除,如果不能直接返回false
- 然后寻找第一个位置i,使得prefix[i] == total/3
- 接着寻找第一个位置j > i,使得prefix[j] == 2*total/3
- 如果i和j都存在且j不是最后一个元素,则返回true
2.3 算法流程
- 计算数组总和total
- 如果total % 3 != 0,返回false
- 初始化target = total / 3
- 遍历prefix数组:
- 当找到prefix[i] == target时记录i
- 当找到prefix[j] == 2*target且j > i时记录j
- 如果找到符合条件的i和j,返回true;否则返回false
3. 代码实现与细节处理
3.1 Python实现示例
python复制def canThreePartsEqualSum(nums):
total = sum(nums)
if total % 3 != 0:
return False
target = total // 3
current_sum = 0
count = 0
for num in nums:
current_sum += num
if current_sum == target:
count += 1
current_sum = 0
if count == 2 and num is not nums[-1]:
return True
return False
3.2 关键细节说明
-
提前终止条件:当累计找到两个满足条件的分割点后,且当前不是最后一个元素时,可以直接返回true,无需继续遍历。
-
重置current_sum:每次找到目标值后需要重置current_sum为0,开始计算下一段的和。
-
边界检查:必须确保第二个分割点不是数组的最后一个元素,否则第三段会为空。
4. 复杂度分析与优化空间
4.1 时间复杂度
- 计算总和:O(n)
- 单次遍历:O(n)
- 总体:O(n)
4.2 空间复杂度
- 不使用额外空间存储前缀和数组:O(1)
- 仅用几个变量记录状态
4.3 可能的优化方向
-
并行计算:对于超大数组,可以考虑将数组分成若干块并行计算前缀和。
-
流式处理:如果数据是以流的形式输入,无法随机访问,此算法依然适用。
5. 常见错误与调试技巧
5.1 典型错误案例
-
未检查总和是否能被3整除:
python复制# 错误示例 def canThreePartsEqualSum(nums): target = sum(nums) / 3 # 应该使用整除// # ... -
忽略第三段非空检查:
python复制# 错误示例 if count == 2: # 缺少对j位置的检查 return True -
错误重置current_sum:
python复制# 错误示例 if current_sum == target: count += 1 # 忘记重置current_sum = 0
5.2 调试建议
-
使用小数组手动验证:
- 输入:[0,0,0,0]
- 预期输出:true
- 解释:可以分成[0],[0],[0,0]
-
边界测试用例:
- 输入:[1,-1,1,-1,1,-1,1,-1]
- 预期输出:true
- 解释:可以分成[1,-1],[1,-1],[1,-1,1,-1]
-
极端情况:
- 输入:[1,1]
- 预期输出:false
- 解释:无法分成三部分
6. 算法扩展与变种问题
6.1 K段等和分割
将问题扩展为能否将数组分成K个连续子数组且和相等。解法思路类似:
- 检查总和是否能被K整除
- 寻找K-1个分割点,使得prefix[i] = i*target (i=1..K-1)
6.2 返回具体分割位置
修改题目要求返回具体分割点i和j的位置。需要在算法中记录这些位置而非仅计数。
6.3 最大/最小分割和差
寻找分割点使得三个子数组的和的极差最小。这需要更复杂的动态规划解法。
7. 实际应用场景
这种分段求和的技巧在实际开发中有广泛应用:
- 负载均衡:将任务分成多个批次,确保每批处理量均衡
- 数据分片:大数据处理时将数据均匀分配到不同节点
- 资源分配:将有限资源分成若干等份分配给不同模块
8. 个人实战心得
在解决这类数组分割问题时,有几点经验值得分享:
-
前缀和数组是处理子数组求和问题的利器,能有效将O(n²)问题降为O(n)
-
提前计算总和可以快速排除不可能的情况(如总和不能被3整除)
-
单次遍历时维护状态变量(如current_sum, count)比存储整个前缀和数组更节省空间
-
边界条件往往比主逻辑更容易出错,需要特别关注:
- 空子数组情况
- 全零数组
- 分割点在首尾位置
-
测试用例设计应当包含:
- 常规能分割的情况
- 不能分割的情况
- 极端情况(小数组、全同元素)
- 包含正负数的case
这道题看似简单,但要想写出健壮的代码,需要充分考虑各种边界情况。建议在面试中遇到类似问题时,先明确表述算法思路,再逐步实现,最后用测试用例验证。
