在解决力扣第48题“旋转图像”时,题目要求将一个 n × n
的矩阵原地顺时针旋转90度(即不使用额外空间)。我采用的是一种直接通过交换四个位置元素的方法来实现旋转。这种方法高效且直观,下面详细解释我的思路和代码。
问题分析
给定一个 n × n
的二维矩阵,我们需要将其顺时针旋转90度。旋转后的矩阵需要满足:
- 矩阵元素的位置变化规律:原始位置
(i, j)
的元素会移动到(j, n-1-i)
。 - 必须在原矩阵上修改,空间复杂度为
O(1)
。
核心思想:
核心思路:四个元素一组循环交换
观察旋转的规律,我们可以发现矩阵中的四个元素会构成一个循环:
- 原始位置
(i, j)
移动到(j, n-1-i)
- 位置
(j, n-1-i)
移动到(n-1-i, n-1-j)
- 位置
(n-1-i, n-1-j)
移动到(n-1-j, i)
- 位置
(n-1-j, i)
移动到(i, j)
这个过程形成了一个闭环,我们可以用一个临时变量 temp
来辅助这四个位置的元素交换:
temp = matrix[i][j];
matrix[i][j] = matrix[n-1-j][i];
matrix[n-1-j][i] = matrix[n-1-i][n-1-j];
matrix[n-1-i][n-1-j] = matrix[j][n-1-i];
matrix[j][n-1-i] = temp;
遍历范围的设计
如果直接遍历整个矩阵,每个元素会被交换两次(导致恢复原状)。我们需要确保每个元素只被交换一次。解决方案是只遍历矩阵的左上角区域:
- 行索引
i
:从0
到n/2
(不包括n/2
)。 - 列索引
j
:从0
到(n+1)/2
(不包括(n+1)/2
)。
为什么列的范围是 (n+1)/2
?
- 当
n
是偶数时,遍历左上角的四分之一区域:i
的范围:[0, n/2)
j
的范围:[0, n/2)
- 当
n
是奇数时,中心元素无需旋转,但中心列的上半部分需要处理:- 列的范围扩展到
(n+1)/2
,确保覆盖中心列左侧的所有列(见下图)。
- 列的范围扩展到
示例:n=3(奇数)
需要遍历的位置:(0,0) 和 (0,1)
(0,0) -> (2,0) -> (2,2) -> (0,2)
(0,1) -> (1,0) -> (2,1) -> (1,2)
中心点 (1,1) 保持不变。
代码实现
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length; // 矩阵尺寸 n x n
// 遍历左上角区域
for (int i = 0; i < n / 2; i++) {
for (int j = 0; j < (n + 1) / 2; j++) {
// 保存左上角元素
int temp = matrix[i][j];
// 左下角元素 -> 左上角
matrix[i][j] = matrix[n - 1 - j][i];
// 右下角元素 -> 左下角
matrix[n - 1 - j][i] = matrix[n - 1 - i][n - 1 - j];
// 右上角元素 -> 右下角
matrix[n - 1 - i][n - 1 - j] = matrix[j][n - 1 - i];
// 临时变量(原左上角)-> 右上角
matrix[j][n - 1 - i] = temp;
}
}
}
}
示例解析
以 3x3
矩阵为例:
初始矩阵:
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
第一步:旋转 (0,0)
所在的组:
(0,0)
→(2,0)
→(2,2)
→(0,2)
→(0,0)
- 交换后:
[7, 2, 1]
[4, 5, 6]
[9, 8, 3]
第二步:旋转 (0,1)
所在的组:
(0,1)
→(1,0)
→(2,1)
→(1,2)
→(0,1)
- 交换后:
[7, 4, 1]
[8, 5, 2]
[9, 6, 3]
最终得到顺时针旋转90度的结果。
复杂度分析
- 时间复杂度:
O(n²)
,每个元素被访问一次。 - 空间复杂度:
O(1)
,仅使用常数级额外空间。
总结
通过将矩阵分为四个位置的循环组,并巧妙设计遍历范围(左上角区域),我们在原地完成了矩阵旋转。这种方法避免了额外的空间开销,且逻辑清晰高效。理解位置映射关系 (i, j) → (j, n-1-i)
是解决本题的关键。