给定一个 N×N
的二维矩阵,其中包含 1
到 N^2
的所有互不相同正整数。
允许的操作为:
每次选择矩阵中的一个元素,将其与它在顺时针螺旋顺序中的下一个元素进行交换。
目标是通过若干次操作,使矩阵变为顺时针螺旋递增顺序,即按照螺旋遍历时,元素依次为 1, 2, 3, ..., N^2
。
请你求出将给定矩阵转换为顺时针螺旋递增顺序所需的最小操作次数,并对 1000000007
取模。
第一行为整数 N (1 ≤ N ≤ 10^3)
。
接下来 N
行,每行 N
个整数,表示初始矩阵。
一个整数,表示最少的操作次数,结果对 1000000007
取模。
转换问题:
将二维矩阵转化为顺时针螺旋遍历的一维数组。
目标数组为:[1, 2, ..., N*N]
。
操作规则分析:
每次操作只能把一个元素与螺旋顺序中下一个元素交换。
也就是说,我们只能进行相邻元素的交换,这就转化为冒泡排序或逆序对统计问题。
核心转化:
我们只需要统计当前螺旋序列中的逆序对数量。
每次相邻交换可以消除一个逆序对,所以答案就是逆序对的个数。
实现方式:
用归并排序统计逆序对,时间复杂度 O(N^2 log N)
。
用方向数组实现螺旋遍历。
import java.util.*;
public class SpiralSwapSorter {
// 模数
static final int MOD = 1000000007;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 读取矩阵大小
int N = Integer.parseInt(sc.nextLine());
int[][] matrix = new int[N][N];
// 读取矩阵内容
for (int i = 0; i < N; i++) {
String[] row = sc.nextLine().split(" ");
for (int j = 0; j < N; j++) {
matrix[i][j] = Integer.parseInt(row[j]);
}
}
// 获取螺旋遍历顺序的一维数组
int[] spiral = spiralOrder(matrix, N);
// 统计逆序对个数
long result = countInversions(spiral);
// 输出答案取模
System.out.println(result % MOD);
}
// 螺旋顺序遍历函数
private static int[] spiralOrder(int[][] matrix, int N) {
int[] res = new int[N * N];
boolean[][] visited = new boolean[N][N];
// 顺时针方向:右、下、左、上
int[] dx = {0, 1, 0, -1};
int[] dy = {1, 0, -1, 0};
int dir = 0; // 当前方向
int x = 0, y = 0, idx = 0;
for (int i = 0; i < N * N; i++) {
res[idx++] = matrix[x][y];
visited[x][y] = true;
// 计算下一个位置
int nx = x + dx[dir];
int ny = y + dy[dir];
// 判断是否需要转向
if (nx < 0 || ny < 0 || nx >= N || ny >= N || visited[nx][ny]) {
dir = (dir + 1) % 4;
nx = x + dx[dir];
ny = y + dy[dir];
}
x = nx;
y = ny;
}
return res;
}
// 使用归并排序统计逆序对
public static long countInversions(int[] arr) {
int[] temp = new int[arr.length];
return mergeSortAndCount(arr, temp, 0, arr.length - 1);
}
// 归并排序 + 逆序对统计
private static long mergeSortAndCount(int[] arr, int[] temp, int left, int right) {
if (left >= right) return 0;
int mid = (left + right) / 2;
long invCount = 0;
invCount += mergeSortAndCount(arr, temp, left, mid);
invCount += mergeSortAndCount(arr, temp, mid + 1, right);
invCount += mergeAndCount(arr, temp, left, mid, right);
return invCount % MOD;
}
// 合并两个有序段并统计逆序对
private static long mergeAndCount(int[] arr, int[] temp, int left, int mid, int right) {
int i = left, j = mid + 1, k = left;
long invCount = 0;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
invCount += (mid - i + 1); // arr[i] > arr[j] 说明 arr[i..mid] 都大于 arr[j]
}
}
while (i <= mid) temp[k++] = arr[i++];
while (j <= right) temp[k++] = arr[j++];
for (int p = left; p <= right; p++) {
arr[p] = temp[p];
}
return invCount % MOD;
}
}
3
3 2 1
6 5 4
9 8 7
3 2 1 4 7 8 9 6 5
10
本题实质是将矩阵顺时针展开,转化为一维排列;
操作限制为“只能与顺时针下一个交换”,变成统计逆序对;
使用归并排序高效统计逆序对;
时间复杂度:O(N^2 log N)
,空间复杂度 O(N^2)
;
支持最大 N = 1000
的规模。