从暴力到优化:如何统计符合特殊条件的三元子数组
一、问题引入:一个看似简单的数组统计问题
作为程序员,我们在日常工作中经常会遇到需要统计数组中特定子序列的问题。今天我们要讨论的是LeetCode上第3392题——"统计符合条件长度为3的子数组数目"。
问题描述:给定一个整数数组nums,返回所有长度为3的子数组,其中第一个数和第三个数的和恰好等于第二个数的一半。子数组指的是数组中连续的元素序列。
示例说明:
-
输入:[1,2,1,2,4,2,4]
-
输出:3
-
解释:满足条件的子数组为:
[1,2,1] (1+1=2,2/2=1)
[2,4,2] (2+2=4,4/2=2)
[4,2,4] (4+4=8,8/2=4)
二、暴力解法:最直观的三重循环
2.1 最朴素的解决思路
对于初学者来说,最直观的解法就是使用三重循环枚举所有可能的三元组:
java
public int countValidSubarrays(int[] nums) {int count = 0;int n = nums.length;for (int i = 0; i < n; i++) {for (int j = i + 1; j < n; j++) {for (int k = j + 1; k < n; k++) {if (j - i == 1 && k - j == 1) { // 确保连续if (nums[i] + nums[k] == nums[j] / 2 && nums[j] % 2 == 0) {count++;}}}}}return count; }
2.2 暴力解法的问题
这种解法虽然正确,但存在明显缺陷:
-
时间复杂度高达O(n³),当n较大时性能极差
-
内层循环做了大量不必要的检查(很多组合根本不连续)
-
条件判断过于复杂
三、优化思路:滑动窗口法
3.1 发现规律
仔细观察题目要求,我们发现:
-
只需要长度为3的连续子数组
-
每次只需要检查三个连续元素的关系
-
不需要考虑不连续的元素组合
3.2 滑动窗口解法
我们可以使用滑动窗口技术,只需一次遍历:
java
public int countValidSubarrays(int[] nums) {int count = 0;for (int i = 0; i < nums.length - 2; i++) {int a = nums[i], b = nums[i+1], c = nums[i+2];// 使用乘法避免除法精度问题if (2 * (a + c) == b) {count++;}}return count; }
3.3 复杂度分析
优化后的解法:
-
时间复杂度:O(n),只需一次遍历
-
空间复杂度:O(1),只使用了常数空间
四、边界条件与注意事项
4.1 边界情况处理
-
数组长度小于3时直接返回0
-
整数除法问题:确保中间数是偶数或者使用乘法形式
-
数组元素可能为负数的情况
4.2 测试用例设计
好的测试用例应该包括:
java
// 常规情况 [1,2,1,2,4,2,4] → 3 // 边界情况 [1,2] → 0 (长度不足) [1,2,1] → 1 // 负数情况 [0,-2,1,3,-4,-2] → 2 // 大数情况 [Integer.MAX_VALUE, -2, Integer.MIN_VALUE] → 0
五、算法优化与变种
5.1 使用乘法替代除法
为了避免整数除法带来的精度问题,我们可以将条件改写为乘法形式:
java
// 原条件:a + c == b / 2 // 优化后: 2 * (a + c) == b
5.2 通用化思考
如果问题改为长度为k的子数组,该如何解决?这时可能需要更复杂的滑动窗口或前缀和技巧。
六、实际应用场景
这类数组统计问题在实际开发中有广泛应用:
-
金融数据分析中的模式识别
-
信号处理中的特征提取
-
生物信息学中的序列分析
七、总结与思考题
7.1 关键点总结
-
理解子数组的连续特性很关键
-
从暴力解法到优化解法的思考过程很重要
-
边界条件和测试用例不可忽视
7.2 留给读者的思考题
-
如果条件改为"第一个数和第二个数的和是第三个数的一半",该如何修改代码?
-
如果要统计所有满足条件的子数组(不限于长度3),该如何解决?
-
如果数组非常大(超过10^6元素),如何进一步优化?
希望这篇文章能帮助你理解这类数组统计问题的解决方法。如果你有更好的解法或者任何疑问,欢迎在评论区留言讨论!