1. 概述
本文将介绍两种统计数组中符号变化次数的方法。首先,我们通过直观的遍历算法实现——遍历数组时,每当检测到符号变化就增加计数器。然后,我们将利用Java Stream以函数式编程风格实现相同功能。过程中会讨论零值处理等边界情况,最终实现高效且可读的解决方案。
2. 迭代法实现
先从简单的循环实现开始,检查每对相邻元素的符号差异。为保持示例统一,使用如下数组:
int[] sampleArray = {1, -2, -3, 4, 0, -1, 5};
实现方法将比较当前元素与前一个非零元素的符号,检测到变化时递增计数器:
int countSignChanges(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
int count = 0;
int prevSign = Integer.signum(arr[0]);
for (int i = 1; i < arr.length; i++) {
int currentSign = Integer.signum(arr[i]);
if (currentSign != 0 && prevSign != 0 && currentSign != prevSign) {
count++;
}
if (currentSign != 0) {
prevSign = currentSign;
}
}
return count;
}
⚠️ 关键点解析:
- 使用
Integer.signum()
将值标准化为-1/0/1,便于直接比较 - 跳过零值避免误判符号变化(踩坑提示:零值会干扰符号判断)
- 仅当遇到非零值时更新
prevSign
**算法时间复杂度O(n),空间复杂度O(1)**,适合中等规模数据处理。测试用例验证:
@Test
void givenArray_whenExistsSignChanges_thenReturnSignChangesQuantity() {
int result = countSignChanges(sampleArray);
assertThat(result).isEqualTo(4);
}
✅ 预期结果:4次变化(1→-2,-3→4,4→-1,-1→5),零值被正确忽略
3. Java Stream实现
虽然符号统计本质是迭代任务,但可以用Stream表达相同逻辑:将元素与前驱配对,再过滤符号差异。Stream实现如下:
int countSignChangesStream(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
int[] signs = Arrays.stream(arr)
.map(Integer::signum)
.filter(s -> s != 0)
.toArray();
return (int) IntStream.range(1, signs.length)
.filter(i -> signs[i] != signs[i - 1])
.count();
}
⚠️ 性能提示:
- 声明式代码更简洁,但会创建中间数组和流
- 时间复杂度仍为O(n),但内存消耗高于命令式版本
测试验证与迭代法结果一致:
@Test
void givenArray_whenUsingStreams_thenReturnSignChangesQuantity() {
int result = countSignChangesStream(sampleArray);
assertThat(result).isEqualTo(4);
}
4. 方案对比
两种方案的核心差异总结:
维度 | 迭代法 | Stream法 |
---|---|---|
性能 | ✅ O(n)时间 + O(1)空间 | ❌ O(n)时间 + O(n)空间 |
可读性 | ⚠️ 需理解循环逻辑 | ✅ 声明式更直观 |
适用场景 | 性能敏感/大数据量 | 代码简洁性优先 |
选择建议:
- 迭代法:对性能有硬性要求时(简单粗暴的解决方案)
- Stream法:团队熟悉函数式编程时(现代Java实践)
完整源码见GitHub仓库