今天仍然來分析一個與數組操作相關的算法,是關於二維數組旋轉問題的。
問題描述
原文
You are given an n × n 2D matrix representing an image.
Rotate the image by 90 degrees (clockwise).
Follow up: Could you do this in-place?
大意:給一個 n * n 的二維矩陣表示的圖像,將這個圖像順時針旋轉90°。
例如給一個 3 * 3矩陣,
[
[1,2,3 ]
[4,5,6 ]
[7,8,9 ]
]
順時針旋轉90°後,
[
[7,4,1 ]
[8,5,2 ]
[9,6,3 ]
]
思路分析
首先最容易想到的就是創建一個新的n * n矩陣來存儲結果,然後將原矩陣的對應行存儲到結果矩陣的對應列即可。經過簡單分析,可以發現原矩陣中(i,j)位置的元素,在結果矩陣中位置應該是(j,n-1-i)。有了對應關係我們可以很快寫出如下的代碼:
/*
* 模擬法:時間複雜度 O(n²) ,空間複雜度 O(n²)
*/
public static void rotate5(int[][] matrix) {
int m = matrix.length;
int n = matrix[0].length;
if (m != n)
return;
int[][] result = new int[n][n];
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) {
result[j][n - 1 - i] = matrix[i][j];
}
//print(result);//打印結果(方法不再給出)
}
說明:這種方法的優勢就是容易理解,算法簡單,不足就是需要額外佔用輔助空間。
算法改進
如果不申請輔助空間,直接在原矩陣上操作,那麼我們應該怎麼做呢?如果仍按照上述的操作直接賦值,那麼就會直接覆蓋掉原本數組當中的元素,數據信息將丟失。換一種思路,矩陣順時針反轉90度之後的結果不過是將數組掉了個方向而已,其實通過矩陣的對稱翻轉操作其實也可以實現這種掉個兒的最終效果,所不同的是順時針翻轉90度不能通過一次簡單的對稱翻轉操作得到,可能需要多次。現在我們需要找到這種對稱翻轉操作和順時針翻轉90度效果之間的過渡方法。
矩陣的對稱翻轉操作分爲兩種,對角線翻轉(主,副) 和 中線翻轉(水平,垂直),通過這兩種翻轉的組合可以達到順時針翻轉90度的效果。例如,我們對最簡單的二維數組{{1,2},{3,4}}分析,按主對角線翻轉一次後,得到{{1,3},{2,4}},再按垂直中線翻轉一次後,得到{{3,1},{4,2}},可以看到這就是我們需要的結果。對翻轉操作做排列組合篩選後,我們一共可以找到4種方法來得到最終結果。
-
方法1:先按主對角線翻轉,再按垂直中線翻轉。
/* * 反轉圖像1:主對角線反轉,垂直中線反轉 時間複雜度 O(n^2) ,空間複雜度 O(1) */ public static void rotate1(int[][] matrix) { int m = matrix.length; int n = matrix[0].length; if (m != n) return; int temp; for (int i = 0; i < n; i++) for (int j = 0; j < i; j++) { temp = matrix[i][j]; matrix[i][j] = matrix[j][i]; matrix[j][i] = temp; } System.out.println("主對角線反轉後:"); print(matrix); for (int i = 0; i < n; i++) for (int j = 0; j < n / 2; j++) { temp = matrix[i][j]; matrix[i][j] = matrix[i][n - 1 - j]; matrix[i][n - 1 - j] = temp; } System.out.println("垂直中線反轉後:"); print(matrix); }
-
方法2:先按副對角線翻轉,再按水平中線翻轉
/* * 副對角線反轉,水平中線反轉 時間複雜度 O(n^2) ,空間複雜度 O(1) */ public static void rotate2(int[][] matrix) { int m = matrix.length; int n = matrix[0].length; if (m != n) return; int temp; for (int i = 0; i < n; i++) for (int j = 0; j < n - 1 - i; j++) { temp = matrix[i][j]; matrix[i][j] = matrix[n - 1 - j][n - 1 - i]; matrix[n - 1 - j][n - 1 - i] = temp; } System.out.println("副對角線反轉後:"); print(matrix); for (int i = 0; i < n / 2; i++) for (int j = 0; j < n; j++) { temp = matrix[i][j]; matrix[i][j] = matrix[n - 1 - i][j]; matrix[n - 1 - i][j] = temp; } System.out.println("水平中線反轉後:"); print(matrix); }
-
方法3:先按水平中線翻轉,再按主對角線翻轉
/* * 水平中線反轉,主對角線反轉 時間複雜度 O(n^2) ,空間複雜度 O(1) */ public static void rotate3(int[][] matrix) { int m = matrix.length; int n = matrix[0].length; if (m != n) return; int temp; for (int i = 0; i < n / 2; i++) for (int j = 0; j < n; j++) { temp = matrix[i][j]; matrix[i][j] = matrix[n - 1 - i][j]; matrix[n - 1 - i][j] = temp; } System.out.println("水平中線反轉後:"); print(matrix); for (int i = 0; i < n; i++) for (int j = 0; j < i; j++) { temp = matrix[i][j]; matrix[i][j] = matrix[j][i]; matrix[j][i] = temp; } System.out.println("主對角線反轉後:"); print(matrix); }
-
方法4:先按垂直中線翻轉,再按副對角線翻轉
/* * 垂直中線反轉,副對角線反轉 時間複雜度 O(n^2) ,空間複雜度 O(1) */ public static void rotate4(int[][] matrix) { int m = matrix.length; int n = matrix[0].length; if (m != n) return; int temp; for (int i = 0; i < n; i++) for (int j = 0; j < n / 2; j++) { temp = matrix[i][j]; matrix[i][j] = matrix[i][n - 1 - j]; matrix[i][n - 1 - j] = temp; } System.out.println("垂直中線反轉後:"); print(matrix); for (int i = 0; i < n; i++) for (int j = 0; j < n - 1 - i; j++) { temp = matrix[i][j]; matrix[i][j] = matrix[n - 1 - j][n - 1 - i]; matrix[n - 1 - j][n - 1 - i] = temp; } System.out.println("副對角線反轉後:"); print(matrix); }
說明:以上四種方法思路基本類似,關鍵在於找出對稱情況下,元素(i,j)與對稱翻轉後新位置(m,n)之間的數量關係。另外,代碼提到的print(int[][] martrix)打印數組的方法不再給出,讀者可以自行補充。