48. 旋转图像
题目来源
题目描述
给定一个 n x n 的二维矩阵表示一个图像,请将图像顺时针旋转 90 度。
要求:
必须在 原地 旋转图像,这意味着需要直接修改输入的二维矩阵,而不能使用另一个矩阵来辅助旋转。
题目分析
难度
- 中等
标签
- 矩阵
- 数学
限制条件
1 <= matrix.length == matrix[i].length <= 201 <= n <= 20-1000 <= matrix[i][j] <= 1000
解题思路
思路一:分层旋转法
通过观察可以发现,矩阵的旋转可以分解为对每一层的边界元素进行旋转。例如,对于一个 4x4 的矩阵,可以将其分为外层和内层两部分,分别处理每层的旋转。
核心算法步骤
-
分层遍历:
- 使用变量
i表示当前层的左上角索引。 - 每一层的边长为
len = n - (i << 1)(即当前层的宽度)。 - 遍历该层的四个边界,依次交换对应位置的元素。
- 使用变量
-
元素交换:
- 对于每个位置
(i, i+j),找到其对应的三个位置,并按顺序交换值。 - 具体来说,四个位置分别为:
- 左上角:
(i, i+j) - 左下角:
(i+len-j-1, i) - 右下角:
(i+len-1, i+len-j-1) - 右上角:
(i+j, i+len-1)
- 左上角:
- 对于每个位置
-
逐层收缩:
- 处理完当前层后,进入下一层(
i++),直到所有层都处理完毕。
- 处理完当前层后,进入下一层(
思路二:数学变换法
通过数学推导可以发现,顺时针旋转 90 度等价于以下两个操作的组合:
- 沿主对角线翻转:将矩阵沿主对角线对称翻转。
- 上下翻转:将矩阵沿水平中轴翻转。
这种方法的优势在于逻辑清晰,代码实现简单。
代码实现
方法一:分层旋转法
/**
* 48. 旋转图像
* @param matrix 二维矩阵
*/
public void rotate(int[][] matrix) {
int n = matrix.length;
for (int i = 0; i <= (n >> 1); i++) { // 分层遍历
int len = n - (i << 1); // 当前层的边长
for (int j = 0; j < len - 1; j++) { // 遍历当前层的四个边界
int temp = matrix[i][i + j];
matrix[i][i + j] = matrix[i + len - j - 1][i];
matrix[i + len - j - 1][i] = matrix[i + len - 1][i + len - j - 1];
matrix[i + len - 1][i + len - j - 1] = matrix[i + j][i + len - 1];
matrix[i + j][i + len - 1] = temp;
}
}
}
方法二:数学变换法
/**
* 48. 旋转图像
* @param matrix 二维矩阵
*/
public void rotate2(int[][] matrix) {
int n = matrix.length;
// 沿主对角线翻转
for (int i = 0; i < n; i++) {
for (int j = 0; j < n - i; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[n - j - 1][n - i - 1];
matrix[n - j - 1][n - i - 1] = temp;
}
}
// 上下翻转
for (int i = 0; i < (n >> 1); i++) {
for (int j = 0; j < n; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[n - i - 1][j];
matrix[n - i - 1][j] = temp;
}
}
}
代码解读
方法一解读
- 分层遍历:通过
i控制当前层的起始位置,len计算当前层的边长。 - 元素交换:通过四个位置的坐标关系,依次交换值,完成当前层的旋转。
- 逐层收缩:每次处理完一层后,进入下一层,直到所有层处理完毕。
方法二解读
- 主对角线翻转:将矩阵沿主对角线对称翻转,相当于交换
(i, j)和(j, i)的值。 - 上下翻转:将矩阵沿水平中轴翻转,相当于交换第
i行和第n-i-1行的值。
性能分析
时间复杂度
- 方法一:O(n²),其中
n是矩阵的边长。每个元素被访问一次。 - 方法二:O(n²),同样每个元素被访问一次。
空间复杂度
- 方法一:O(1),只使用了常数级别的额外空间。
- 方法二:O(1),同样只使用了常数级别的额外空间。
测试用例
以下是几个测试用例,用于验证代码的正确性:
// 测试用例1
int[][] matrix1 = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
rotate(matrix1);
System.out.println(Arrays.deepToString(matrix1));
// 输出: [[7, 4, 1], [8, 5, 2], [9, 6, 3]]
// 测试用例2
int[][] matrix2 = {
{5, 1, 9, 11},
{2, 4, 8, 10},
{13, 3, 6, 7},
{15, 14, 12, 16}
};
rotate(matrix2);
System.out.println(Arrays.deepToString(matrix2));
// 输出: [[15, 13, 2, 5], [14, 3, 4, 1], [12, 6, 8, 9], [16, 7, 10, 11]]
// 测试用例3
int[][] matrix3 = {
{1}
};
rotate(matrix3);
System.out.println(Arrays.deepToString(matrix3));
// 输出: [[1]]
扩展讨论
方法对比
- 分层旋转法:适合对矩阵的边界操作有直观理解的开发者,逻辑较为复杂,但性能优秀。
- 数学变换法:逻辑清晰,代码简洁,适合快速实现,且易于扩展到其他类似问题。
实际应用场景
- 图像处理:旋转图像是计算机视觉中的常见操作。
- 数据转换:矩阵旋转可用于数据的重新排列和格式化。
总结
本题考察了对矩阵操作的理解和实现能力。通过两种不同的方法,我们展示了如何高效地解决矩阵旋转问题。无论是分层旋转法还是数学变换法,都有其独特的优点,开发者可以根据实际需求选择合适的方法。

1万+

被折叠的 条评论
为什么被折叠?



