1. 概述

数组操作是编程中的基础技能,无论在什么应用中都可能用到。虽然这些操作常被封装在更方便的接口(如 Collections API)后,但作为开发者,我们仍需尽早掌握这些基础知识。

本文将探讨如何在数组中查找最小元素的索引。我们将讨论适用于不同元素类型的方法,但为简化说明,主要使用整数数组作为示例。

2. 简单迭代法

最简单的解决方案往往是最可靠的——它更容易实现、修改和理解。下面展示如何用基本的 for 循环实现:

@ParameterizedTest
@MethodSource("primitiveProvider")
void givenArray_whenUsingForLoop_thenGetCorrectResult(int[] array, int expectedIndex) {
    int minValue = Integer.MAX_VALUE;
    int minIndex = -1;
    for (int i = 0; i < array.length; i++) {
        if (array[i] < minValue) {
            minValue = array[i];
            minIndex = i;
        }
    }
    assertThat(minIndex).isEqualTo(expectedIndex);
}

这段代码虽然略显冗长,但目标明确且健壮:优先解决问题而非追求代码行数最少。对新手友好的同时,也不依赖高级 Java API。

✅ 关键点:循环体中同时跟踪最小值及其索引,避免重复遍历

循环类型可替换whiledo-while 同样适用。处理引用类型时,需确保对象实现了 Comparable,并用 compareTo() 替代 < 运算符。

3. 两步分离法

将任务拆分为两步:先找最小元素,再定位其索引。虽然性能略低于单次遍历,但时间复杂度仍为 O(n):

@ParameterizedTest
@MethodSource("primitiveProvider")
void givenArray_whenUsingForLoopAndLookForIndex_thenGetCorrectResult(int[] array, int expectedIndex) {
    int minValue = Integer.MAX_VALUE;
    for (int number : array) {
        if (number < minValue) {
            minValue = number;
        }
    }
    int minIndex = -1;
    for (int i = 0; i < array.length; i++) {
        if (array[i] == minValue) {
            minIndex = i;
            break;
        }
    }
    assertThat(minIndex).isEqualTo(expectedIndex);
}

⚠️ 注意:第二个循环使用 break 提前退出,但整体性能并未优于单次遍历方案

4. 原生流处理

用 Stream API 消除第一个循环。这里使用 IntStream 处理基本类型数组:

@ParameterizedTest
@MethodSource("primitiveProvider")
void givenArray_whenUsingIntStreamAndLookForIndex_thenGetCorrectResult(int[] array, int expectedIndex) {
    int minValue = Arrays.stream(array).min().orElse(Integer.MAX_VALUE);
    int minIndex = -1;
    for (int i = 0; i < array.length; i++) {
        if (array[i] == minValue) {
            minIndex = i;
            break;
        }
    }
    assertThat(minIndex).isEqualTo(expectedIndex);
}

IntStream 提供了原生值序列的便捷操作,将命令式循环转为声明式流。进一步优化第二个循环:

@ParameterizedTest
@MethodSource("primitiveProvider")
void givenArray_whenUsingIntStreamAndLookForIndexWithIntStream_thenGetCorrectResult(int[] array, int expectedIndex) {
    int minValue = Arrays.stream(array).min().orElse(Integer.MAX_VALUE);
    int minIndex = IntStream.range(0, array.length)
      .filter(index -> array[index] == minValue)
      .findFirst().orElse(-1);
    assertThat(minIndex).isEqualTo(expectedIndex);
}

❌ 坑:虽然声明式风格更优雅,但可读性显著下降,尤其对 Stream 经验不足的开发者

依赖库方案:用 Apache Commons 的 ArrayUtils 提升可读性:

@ParameterizedTest
@MethodSource("primitiveProvider")
void givenArray_whenUsingIntStreamAndLookForIndexWithArrayUtils_thenGetCorrectResult(int[] array, int expectedIndex) {
    int minValue = Arrays.stream(array).min().orElse(Integer.MAX_VALUE);
    int minIndex = ArrayUtils.indexOf(array, minValue);
    assertThat(minIndex).isEqualTo(expectedIndex);
}

或转换为 List 利用其 indexOf() 方法:

@ParameterizedTest
@MethodSource("referenceTypesProvider")
void givenArray_whenUsingReduceAndList_thenGetCorrectResult(Integer[] array, int expectedIndex) {
    List<Integer> list = Arrays.asList(array);
    int minValue = list.stream().reduce(Integer.MAX_VALUE, Integer::min);
    int index = list.indexOf(minValue);
    assertThat(index).isEqualTo(expectedIndex);
}

⚠️ 空间复杂度警告:数组转 List 会将空间复杂度从 O(1) 提升至 O(n),不推荐使用

5. 引用类型数组处理

原生流不适用于引用类型,改用 reduce() 方法:

@ParameterizedTest
@MethodSource("referenceTypesProvider")
void givenArray_whenUsingReduce_thenGetCorrectResult(Integer[] array, int expectedIndex) {
    int minValue = Arrays.stream(array).reduce(Integer.MAX_VALUE, Integer::min);
    int minIndex = ArrayUtils.indexOf(array, minValue);
    assertThat(minIndex).isEqualTo(expectedIndex);
}

reduce() 接收初始值(Integer.MAX_VALUE)和 min() 方法引用。此处非常规用法:实际是过滤而非聚合。可搭配 ArrayUtilsfilter() 使用。

6. 流中直接处理索引

在 Stream 中直接操作索引,将逻辑封装在 reduce() 内:

@ParameterizedTest
@MethodSource("primitiveProvider")
void givenArray_whenUsingReduceWithRange_thenGetCorrectResult(int[] array, int expectedIndex) {
    int index = IntStream.range(0, array.length)
      .reduce((a, b) -> array[a] <= array[b] ? a : b)
      .orElse(-1);
    assertThat(index).isEqualTo(expectedIndex);
}

❌ 踩坑:此方案可读性极差,要求开发者对 Stream API 有深度理解

7. 总结

数组是 Java 最基础的数据结构。虽然日常开发中可能不直接操作数组,但熟练掌握其操作仍是必备技能。

简单直接的方法通常最优:它们明确易懂且易于维护。使用 Stream API 需要函数式编程基础,可能提升或降低代码可读性,需谨慎使用。

✅ 最佳实践:优先选择简单迭代法,仅在团队熟悉 Stream 且代码可读性可控时使用流式方案

本文所有代码示例可在 GitHub 获取。


原始标题:Finding the Index of the Smallest Element in an Array | Baeldung