1. 引言

本文将探讨反转二维数组行的问题,并提供几种基于Java内置库的解决方案。这是一个在数组处理中常见的操作需求。

2. 问题理解

二维数组是程序员经常处理的数据结构。例如:

  • 金融电子表格软件通常用二维数组存储数据,每个位置代表数字或文本
  • 数字艺术和摄影领域,图像常被存储为二维数组,每个位置代表颜色强度

操作二维数组时,反转所有行是一种常见需求。例如:

  • Google Sheets提供了行反转功能
  • 通过反转图像的所有行,可以找到其垂直对称部分

另一个应用场景是在Java中处理Stream时,需要创建反转版本的Stream或按反转顺序收集到其他集合。

问题定义很简单:反转二维数组的每一行,使首元素与末元素交换位置,依此类推。注意:这里关注的是元素在输入中的出现顺序反转,而非自然顺序反转。

3. 原地反转行

可以将二维数组的每一行视为一个一维数组来理解。一维数组的反转算法很简单:交换当前索引元素与其对称索引元素,直到到达中间元素。

例如:

原始数组: [-5, 4, 3, -2, 7]
反转后: [7, -2, 3, 4, -5]

我们交换了索引0与4、索引1与3的元素。中间元素保持原位。

若对二维数组的每一行应用此算法,即可解决二维数组的行反转问题。

3.1. 使用Java for循环

基于上述思路的代码实现:

public static void reverseRowsUsingSimpleForLoops(int[][] array) {
    for (int row = 0; row < array.length; row++) {
        for (int col = 0; col < array[row].length / 2; col++) {
            int current = array[row][col];
            array[row][col] = array[row][array[row].length - col - 1];
            array[row][array[row].length - col - 1] = current;
        }
    }
}

内层循环实现了一维数组的反转:遍历每个元素,将其与对称位置的元素(位于列索引array[row].length - col - 1)交换,直到到达中间索引。

外层循环对输入数组的每一行应用此算法。

使用JUnit 5和AssertJ验证结果:

@Test
void givenArray_whenCallReverseRows_thenAllRowsReversed() {
    int[][] input = new int[][] { { 1, 2, 3 }, { 3, 2, 1 }, { 2, 1, 3 } };

    int[][] expected = new int[][] { { 3, 2, 1 }, { 1, 2, 3 }, { 3, 1, 2 } };
    reverseRowsUsingSimpleForLoops(input);

    assertThat(input).isEqualTo(expected);
}

3.2. 使用嵌套Java 8 IntStream

与for循环方案类似,可使用Java 8 Stream实现:

public static void reverseRowsUsingStreams(int[][] array) {
    IntStream.range(0, array.length)
      .forEach(row -> IntStream.range(0, array[row].length / 2)
          .forEach(col -> {
              int current = array[row][col];
              array[row][col] = array[row][array[row].length - col - 1];
              array[row][array[row].length - col - 1] = current;
          }));
}

核心逻辑保持不变——仍交换元素与其对称位置元素。

唯一区别是使用IntStream.range()forEach()组合来通过索引遍历流,在内部forEach()的lambda表达式中执行交换操作。

3.3. 使用内置Collections.reverse()方法

也可利用内置的reverse()方法:

public static void reverseRowsUsingCollectionsReverse(int[][] array) {
    for (int row = 0; row < array.length; row++) {
        List <Integer> collectionBoxedRow = Arrays.stream(array[row])
            .boxed()
            .collect(Collectors.toList());

        Collections.reverse(collectionBoxedRow);

        array[row] = collectionBoxedRow.stream()
            .mapToInt(Integer::intValue)
            .toArray();
    }
}

步骤分解:

  1. 遍历原始二维数组
  2. 装箱每行:从int[]转为List<Integer>
  3. 因为Collections.reverse()仅适用于对象集合,且Java没有公开API可原地反转int[]类型
  4. 拆箱反转后的行:使用mapToInt()toArray()方法
  5. 将结果赋回原数组的对应行

若输入参数为List<List<Integer>>,方案会更简洁(无需转换):

public static void reverseRowsUsingCollectionsReverse(List<List<Integer>> array) {
    array.forEach(Collections::reverse);
}

4. 在Stream执行过程中反转行

前述方案均为原地反转。但使用Java Stream时,通常不希望修改原始数据。本节将创建自定义映射器和收集器来反转二维数组的行。

4.1. 创建反转顺序映射器

实现返回反转顺序列表的函数:

static <T> List<T> reverse(List<T> input) {
    Object[] tempArray = input.toArray();

    Stream<T> stream = (Stream<T>) IntStream.range(0, tempArray.length)
        .mapToObj(i -> tempArray[tempArray.length - i - 1]);

    return stream.collect(Collectors.toList());
}

方法特点:

  • 接受泛型List<T>,返回其反转版本
  • 使用泛型支持任意类型的流处理

算法步骤:

  1. 创建临时Object[]数组存储输入内容
  2. 通过重映射每个元素到其对称位置反转元素(类似3.1节)
  3. 收集结果到列表并返回

在二维数组流中使用reverse()

List<List<Integer>> array = asList(asList(1, 2, 3), asList(3, 2, 1), asList(2, 1, 3));

List<List<Integer>> result = array.stream()
  .map(ReverseArrayElements::reverse)
  .collect(Collectors.toList());

reverse()作为原始二维数组流中map()方法的lambda函数,收集反转结果到新二维数组。

4.2. 实现反转顺序收集器

通过自定义收集器实现相同功能:

static <T> Collector<T, ?, List<T>> toReversedList() {
    return Collector.of(
        ArrayDeque::new,
        (Deque<T> deque, T element) -> deque.addFirst(element),
        (d1, d2) -> {
            d2.addAll(d1);
            return d2;
        },
        ArrayList::new
    );
}

收集器解析:

  1. 供应器:创建ArrayDeque辅助反转(因其支持高效首部插入)
  2. 累加器:将每个输入元素添加到累加器ArrayDeque的首部
  3. 组合器:合并两个累加器ArrayDeque(d1和d2),返回d2
  4. 完成器:将最终ArrayDeque转换为ArrayList

核心机制:从左到右读取输入数组,将元素添加到中间ArrayDeque的首部。执行结束时,累加的ArrayDeque将包含反转后的数组,最后转换为列表返回。

在流中使用toReversedList()

List<List<Integer>> array = asList(asList(1, 2, 3), asList(3, 2, 1), asList(2, 1, 3));

List<List<Integer>> result = array.stream()
    .map(a -> a.stream().collect(toReversedList()))
    .collect(Collectors.toList());

toReversedList()直接传入原始二维数组行流的collect()方法,收集反转后的行到新列表生成最终结果。

5. 总结

本文探讨了多种原地反转二维数组行的算法,并创建了用于在流中反转行的自定义映射器和收集器。这些方案覆盖了不同场景下的数组反转需求。

完整源代码可在GitHub仓库获取。


原始标题:Reverse Row of a 2d Array in Java | Baeldung