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();
}
}
步骤分解:
- 遍历原始二维数组
- 装箱每行:从
int[]
转为List<Integer>
- 因为
Collections.reverse()
仅适用于对象集合,且Java没有公开API可原地反转int[]
类型 - 拆箱反转后的行:使用
mapToInt()
和toArray()
方法 - 将结果赋回原数组的对应行
若输入参数为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>
,返回其反转版本 - 使用泛型支持任意类型的流处理
算法步骤:
- 创建临时
Object[]
数组存储输入内容 - 通过重映射每个元素到其对称位置反转元素(类似3.1节)
- 收集结果到列表并返回
在二维数组流中使用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
);
}
收集器解析:
- 供应器:创建
ArrayDeque
辅助反转(因其支持高效首部插入) - 累加器:将每个输入元素添加到累加器
ArrayDeque
的首部 - 组合器:合并两个累加器
ArrayDeque
(d1和d2),返回d2 - 完成器:将最终
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仓库获取。