## 48. 旋转图像(旋转矩阵)
难度:Medium
语言:Java
题目
给定一个 n × n 的二维矩阵表示一个图像。将图像顺时针旋转 90 度。
说明:你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。
示例 1:
给定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋转输入矩阵,使其变为:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
示例 2:
给定 matrix =
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
],
原地旋转输入矩阵,使其变为:
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]
解法1(非原地旋转,借助辅助数组)
思路
先来一个笨办法,借用辅助数组,但不符合题目原地旋转的要求。
根据3x3的矩阵和4x4的矩阵旋转后得到的图像来找规律,可以发现规律:
matrix_new[col][N-row-1] = matrix[row][col]
代码
class Solution1 {
public void rotate(int[][] matrix) {
int i, j, N = matrix.length;
int[][] matrix_new = new int[N][N];
for (i=0; i<N; i++)
for (j=0; j<N; j++)
//key: matrix_new[col][N-row-1] = matrix[row][col]
matrix_new[j][N-i-1] = matrix[i][j];
for (i=0; i<N; i++)
for (j=0; j<N; j++)
matrix[i][j] = matrix_new[i][j];
}
}
复杂度分析
时间复杂度:O(N^2),其中N是matrix的边长。
空间复杂度:O(N^2)。我们需要使用一个和 matrix大小相同的辅助数组。
解法2(官方题解,原地旋转)
思路
根据3x3的矩阵和4x4的矩阵旋转后得到的图像来找规律,可以发现规律:
matrix_new[col][N-row-1] = matrix[row][col]
原地旋转就是要求不创建新矩阵matrix_new,在原有矩阵matrix的基础上进行旋转,这就需要用到一个临时变量:int temp,防止换值时,原来的值被覆盖而丢失。
已知换值的关键等式:
matrix[col][N-row-1] = matrix[row][col]
为了防止**matrix_new[col][N-row-1]**被覆盖,用一个临时变量 temp 暂存matrix_new[col][N-row-1]的值。
temp = matrix[col][N-row-1]
matrix[col][N-row-1] = matrix[row][col]
那么旋转后matrix_new[col][N-row-1]又去了哪里呢?我们根据上面的关键等式,可得到变换关系:
//重要变换等式!!!
row = col
col = N - row - 1
带入关键等式,可得到
matrix[N−row−1][N−col−1]=matrix[col][N−row−1]
为了保存matrix[N−row−1][N−col−1]的值,使用刚才的临时变量temp,这样的话,temp保存了matrix[N−row−1][N−col−1]的值,而给matrix[N−row−1][N−col−1]赋值为matrix[col][N−row−1],使得matrix[col][N−row−1]的值没有丢失,已经取出后,再将matrix[col][N−row−1]赋值给matrix[row][col]
temp = matrix[N−row−1][N−col−1]
matrix[N−row−1][N−col−1] = matrix[col][N−row−1]
matrix[col][N−row−1] = matrix[row][col]
那么,matrix[N−row−1][N−col−1]又旋转去了哪里呢?以此类推
temp = matrix[N-col-1][row]
matrix[N-col-1][row] = matrix[N−row−1][N−col−1]
matrix[N−row−1][N−col−1] = matrix[col][N−row−1]
matrix[col][N−row−1] = matrix[row][col]
matrix[N-col-1][row]又旋转去了哪里呢?再来一次
temp = matrix[row][col]
matrix[row][col] = matrix[N-col-1][row]
matrix[N-col-1][row] = matrix[N−row−1][N−col−1]
matrix[N−row−1][N−col−1] = matrix[col][N−row−1]
matrix[col][N−row−1] = matrix[row][col]
到此为止,我们可以发现已经出现了一个闭合回路,这四项处于一个循环中,并且每一项旋转后的位置就是下一项所在的位置。因此我们可以使用一个临时变量 temp 完成这四项的原地交换:
temp = matrix[row][col]
matrix[row][col] = matrix[N-col-1][row]
matrix[N-col-1][row] = matrix[N−row−1][N−col−1]
matrix[N−row−1][N−col−1] = matrix[col][N−row−1]
matrix[col][N−row−1] = temp
现在已经知道了如何原地旋转矩阵,还有一个重要的问题在于:应该枚举哪些位置进行上述的原地交换操作呢?由于每一次原地交换四个位置,因此可以将矩阵均分为四部分:
- 当n为偶数时,需要枚举 n2 / 4个位置,如下图所示,只用枚举蓝色区域:
用 for 循环表示为:
for (int i=0; i<n/2; i++){ // 行
for(int j=0; j<n/2; j++){ // 列
//此处进行旋转操作
}
}
- 当n为奇数时,中心点不用旋转,需要枚举 (n2 - 1) / 4 个位置,如下图所示,只用枚举蓝色区域:
用 for 循环表示为:
for (int i=0; i<n/2; i++){ // 行
for(int j=0; j<(n+1)/2; j++){ // 列
//此处进行旋转操作
}
}
由以上两种情况的 for 循环可发现,两种情况可以合并:
for (int i=0; i<n/2; i++){ // 行,当 n 为 5 时,5 / 2 = 2
for(int j=0; j<(n+1)/2; j++){ // 列
//当 n 为 5 时, (5 + 1) / 2 = 3; 当 n 为 4 时, (4 + 1) / 2 = 2;
//此处进行旋转操作
}
}
代码
class Solution2 {
public void rotate(int[][] matrix) {
int i, j, temp, N = matrix.length;
for (i=0; i<(N+1)/2; i++)
for (j=0; j<N/2; j++){
temp = matrix[i][j];
matrix[i][j] = matrix[N-j-1][i];
matrix[N-j-1][i] = matrix[N-i-1][N-j-1];
matrix[N-i-1][N-j-1] = matrix[j][N-i-1];
matrix[j][N-i-1] = temp;
}
}
}
解法3(原地旋转,先转置再水平翻转)
思路
一个矩阵沿着左上到右下的对角线翻转(先转置),然后再水平翻转,即将矩阵沿垂直中线反转,就可以等同于顺时针旋转90°。
代码
class Solution {
public void rotate(int[][] matrix) {
int i, j, temp, N = matrix.length;//二维数组的length等于行数
// 转置数组
for (i=0; i<N; i++){
for (j=i; j<N; j++){
temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
// 水平翻转
for (i=0; i<N; i++){
for (j=0; j<N/2; j++){
temp = matrix[i][j];
matrix[i][j] = matrix[i][N-1-j];
matrix[i][N-1-j] = temp;
}
}
}
}