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仓库


原始标题:Count the Number of Sign Changes in an Array | Baeldung