前言
在日常算法学习和面试中,数组划分问题是一个常见的题型。本篇文章将探讨如何判断一个数组是否可以被分为三个和相等的非空部分,并且这三部分在数组中是连续的。
题目
题目分析
问题描述
给定一个整数数组 arr,要求找出两个索引 i 和 j(满足 i + 1 < j),使得:
- 第一部分的和:
arr[0] + arr[1] + ... + arr[i] - 第二部分的和:
arr[i + 1] + arr[i + 2] + ... + arr[j - 1] - 第三部分的和:
arr[j] + arr[j + 1] + ... + arr[arr.length - 1]
这三部分的和相等。
如果存在这样的索引,则返回 true,否则返回 false。
数学分析
为了解决这个问题,我们可以从数学性质入手:
-
数组总和的可分性:
- 如果数组总和
totalSum不能被 3 整除,则不可能将其划分为三个和相等的部分。因此可以直接返回false。
- 如果数组总和
-
目标和:
- 如果总和
totalSum能被 3 整除,则每部分的目标和为target = totalSum / 3。
- 如果总和
-
累加与匹配:
- 我们通过累加数组元素,逐一检查是否能找到和为
target的部分。 - 如果找到两次和为
target的部分,剩余的部分必然也满足条件。
- 我们通过累加数组元素,逐一检查是否能找到和为
例如: 数组 arr = [0, 2, 1, -6, 6, -7, 9, 1, 2, 0, 1]:
- 总和为 9,可以整除 3,目标和为
9 / 3 = 3。 - 遍历过程中,累加到
3时,记录一次。 - 累加到第二个
3时,记录第二次,剩余部分必然满足条件。
思路
-
计算数组总和
如果数组的总和totalSum不能被3整除,则无法划分为三个和相等的部分。直接返回false。
如果能被整除,则每部分的目标和为:target=totalSum3\text{target} = \frac{\text{totalSum}}{3}target=3totalSum
-
遍历数组并累加
我们用一个变量currentSum表示当前部分的累计和,逐个遍历数组:- 当
currentSum == target时,说明找到了一部分,计数器count++。 - 这时,
currentSum被重置为0,开始寻找下一部分。
- 当
-
提前判断是否满足条件
当找到两部分满足currentSum == target且数组未遍历完时,可以提前返回true,因为剩下的部分一定满足条件。 -
特殊情况处理
如果遍历结束后仍未找到两部分,则返回false。
代码
以下是具体实现的代码(基于Java语言)
public class Main {
public static boolean solution(int[] arr) {
int totalSum = 0;
// 计算数组的总和
for (int num : arr) {
totalSum += num;
}
// 如果总和不能被3整除,返回false
if (totalSum % 3 != 0) {
return false;
}
int target = totalSum / 3;
int currentSum = 0;
int count = 0;
// 遍历数组,寻找满足条件的前缀和
for (int i = 0; i < arr.length - 1; i++) {
currentSum += arr[i];
// 如果找到一个目标和
if (currentSum == target) {
count++;
currentSum = 0; // 重置当前和
}
// 如果找到两个目标和,并且剩下的部分也满足条件
if (count == 2 && i < arr.length - 1) {
return true;
}
}
return false;
}
public static void main(String[] args) {
// 测试用例
System.out.println(solution(new int[]{0, 2, 1, -6, 6, -7, 9, 1, 2, 0, 1}) == true ? 1 : 0);
System.out.println(solution(new int[]{1, 1, 1, 1}) == false ? 1 : 0);
System.out.println(solution(new int[]{-1, 3, 2, 5, -2, 2, 5, 1, -9, 0}) == true ? 1 : 0);
}
}
代码分析
1. 计算数组总和
int totalSum = 0;
for (int num : arr) totalSum += num;
功能
- 遍历数组,将所有元素累加,计算数组的总和。
- 用于判断数组是否可以被划分为三部分。
关键点
- 时间复杂度:O(n),需要遍历整个数组。
- 如果数组为空,
totalSum为 0,接下来代码会判断0 % 3 == 0,理论上是允许的,但实际要求非空部分,因此问题中未明确时需留意。
2. 判断总和是否能被 3 整除
if (totalSum % 3 != 0) return false;
功能
- 通过取模操作,快速判断总和是否可以被 3 整除。
- 若不能整除,直接返回
false,无须进行后续的遍历和计算。
关键点
- 此处的剪枝优化非常重要,避免了无效的遍历。
- 如果总和无法被整除,那么无论如何划分,三部分都不可能相等。
3. 查找满足条件的部分
int target = totalSum / 3, currentSum = 0, count = 0;
for (int num : arr) {
currentSum += num;
if (currentSum == target) {
count++;
currentSum = 0;
}
if (count == 2) return true;
}
功能
- 遍历数组,逐步累加
currentSum。 - 每当
currentSum等于目标和target时,说明找到一部分,将计数器count++并重置currentSum。
关键点
-
分段累加:
currentSum += num:动态累计当前部分的和。currentSum == target:表示找到一个完整的部分,重置以便继续查找下一部分。
-
提前返回:
- 当找到两部分时,立即返回
true,无需继续遍历。 - 因为总和已被验证可以被整除,因此剩余部分一定满足条件。
- 当找到两部分时,立即返回
-
逻辑保证:
- 遍历时不包括最后一部分的结束位置,因为只需要找到两部分,剩余部分自动满足。
算法复杂度分析
- 时间复杂度:O(n)
仅需一次遍历数组,计算总和后再通过一次遍历找到划分点。 - 空间复杂度:O(1)
使用常数额外空间存储变量。
最后
通过这一题,我们学习了如何通过简单的数学性质(如整除)来快速判断问题是否可行,并结合前缀和技巧优化遍历过程。这种思想在数组划分问题中非常常用。