【Hot100|19-LeetCode 48. 旋转图像 】

LeetCode 48. 旋转图像 - 完整解法详解

一、问题理解

问题描述

给定一个 n × n 的二维矩阵表示一个图像,将图像顺时针旋转 90 度。必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。

示例

python

复制代码
输入: matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出: [[7,4,1],[8,5,2],[9,6,3]]
解释: 
原矩阵:
[1,2,3]
[4,5,6]
[7,8,9]

旋转后:
[7,4,1]
[8,5,2]
[9,6,3]

输入: 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]]

要求

  • 原地旋转矩阵,不使用额外空间

  • 时间复杂度:O(n²)

  • 空间复杂度:O(1)

二、核心思路:数学变换

基本思想

顺时针旋转 90 度可以通过以下两步实现:

  1. 转置矩阵:将矩阵的行变为列

  2. 水平翻转:将每一行进行反转

数学原理

对于矩阵中的元素 matrix[i][j],顺时针旋转 90 度后的新位置是 matrix[j][n-1-i]

具体来说:

  • 转置操作:matrix[i][j]matrix[j][i]

  • 水平翻转:matrix[j][i]matrix[j][n-1-i]

三、代码逐行解析

方法一:转置 + 翻转(最优解)

Python 解法

python

复制代码
from typing import List

class Solution:
    def rotate(self, matrix: List[List[int]]) -> None:
        """
        不要返回任何内容,原地修改矩阵
        """
        n = len(matrix)
        
        # 第一步:转置矩阵(行列互换)
        # 注意:只需要遍历对角线下方或上方的元素,避免重复交换
        for i in range(n):
            for j in range(i):  # 只遍历对角线下方元素
                matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
        
        # 第二步:水平翻转每一行
        for row in matrix:
            row.reverse()
Java 解法

java

复制代码
class Solution {
    public void rotate(int[][] matrix) {
        int n = matrix.length;
        
        // 第一步:转置矩阵
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < i; j++) {  // 只遍历对角线下方
                int temp = matrix[i][j];
                matrix[i][j] = matrix[j][i];
                matrix[j][i] = temp;
            }
        }
        
        // 第二步:水平翻转每一行
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n / 2; j++) {  // 只遍历前半部分
                int temp = matrix[i][j];
                matrix[i][j] = matrix[i][n - 1 - j];
                matrix[i][n - 1 - j] = temp;
            }
        }
    }
}

方法二:分圈旋转(更直观)

Python 解法

python

复制代码
from typing import List

class Solution:
    def rotate(self, matrix: List[List[int]]) -> None:
        n = len(matrix)
        
        # 分层旋转:从外圈到内圈
        for layer in range(n // 2):
            first = layer
            last = n - 1 - layer
            
            # 旋转当前层的四个边
            for i in range(first, last):
                offset = i - first
                
                # 保存上边
                top = matrix[first][i]
                
                # 左边移动到上边
                matrix[first][i] = matrix[last - offset][first]
                
                # 下边移动到左边
                matrix[last - offset][first] = matrix[last][last - offset]
                
                # 右边移动到下边
                matrix[last][last - offset] = matrix[i][last]
                
                # 上边移动到右边
                matrix[i][last] = top
Java 解法

java

复制代码
class Solution {
    public void rotate(int[][] matrix) {
        int n = matrix.length;
        
        // 分层旋转
        for (int layer = 0; layer < n / 2; layer++) {
            int first = layer;
            int last = n - 1 - layer;
            
            for (int i = first; i < last; i++) {
                int offset = i - first;
                
                // 保存上边
                int top = matrix[first][i];
                
                // 左边移动到上边
                matrix[first][i] = matrix[last - offset][first];
                
                // 下边移动到左边
                matrix[last - offset][first] = matrix[last][last - offset];
                
                // 右边移动到下边
                matrix[last][last - offset] = matrix[i][last];
                
                // 上边移动到右边
                matrix[i][last] = top;
            }
        }
    }
}

四、Java 与 Python 语法对比

1. 矩阵操作

操作 Java Python
获取长度 matrix.length len(matrix)
交换元素 需要临时变量 a, b = b, a
反转列表 需要手动实现 list.reverse()

2. 循环控制

操作 Java Python
遍历行 for (int i=0; i<n; i++) for i in range(n):
遍历对角线下方 for (int j=0; j<i; j++) for j in range(i):
反转数组 需要手动实现 内置方法

3. 矩阵转置

操作 Java Python
转置交换 需要临时变量 一行交换
遍历方式 双层循环 双层循环

五、实例演示

示例:matrix = [[1,2,3],[4,5,6],[7,8,9]]

方法一(转置 + 翻转)步骤:
  1. 原始矩阵

text

复制代码
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
  1. 第一步:转置矩阵(行列互换):

    • 遍历对角线下方元素

    • 交换 (1,0)(0,1):4 ↔ 2

    • 交换 (2,0)(0,2):7 ↔ 3

    • 交换 (2,1)(1,2):8 ↔ 6

    • 转置后矩阵

text

复制代码
[1, 4, 7]
[2, 5, 8]
[3, 6, 9]
  1. 第二步:水平翻转每一行

    • 翻转第一行:[1,4,7][7,4,1]

    • 翻转第二行:[2,5,8][8,5,2]

    • 翻转第三行:[3,6,9][9,6,3]

    • 最终结果

text

复制代码
[7, 4, 1]
[8, 5, 2]
[9, 6, 3]
方法二(分圈旋转)步骤:

对于 n=3,只有一个圈(layer=0):

  • first=0, last=2

  • 遍历 i=01

i=0

  • offset = 0

  • top = matrix[0][0] = 1

  • 左边移动到上边:matrix[0][0] = matrix[2][0] = 7

  • 下边移动到左边:matrix[2][0] = matrix[2][2] = 9

  • 右边移动到下边:matrix[2][2] = matrix[0][2] = 3

  • 上边移动到右边:matrix[0][2] = top = 1

此时矩阵:

text

复制代码
[7, 2, 1]
[4, 5, 6]
[9, 8, 3]

i=1

  • offset = 1

  • top = matrix[0][1] = 2

  • 左边移动到上边:matrix[0][1] = matrix[1][0] = 4

  • 下边移动到左边:matrix[1][0] = matrix[2][1] = 8

  • 右边移动到下边:matrix[2][1] = matrix[1][2] = 6

  • 上边移动到右边:matrix[1][2] = top = 2

最终矩阵:

text

复制代码
[7, 4, 1]
[8, 5, 2]
[9, 6, 3]

六、关键细节解析

1. 为什么转置时只遍历对角线下方元素?

  • 如果遍历整个矩阵,每个元素会被交换两次,最终回到原位置

  • 只遍历对角线下方,确保每个元素只交换一次

  • 对角线上的元素不需要交换(自己和自己交换)

2. 为什么转置 + 翻转等于旋转90度?

  • 转置:将矩阵的行变为列(相当于沿着主对角线翻转)

  • 水平翻转:将每一行左右反转

  • 组合效果:顺时针旋转90度

  • 验证:matrix[i][j]matrix[j][i]matrix[j][n-1-i]

3. 如何理解分圈旋转中的四个步骤?

对于矩阵中的一组四个元素:

  • 上边 → 右边

  • 右边 → 下边

  • 下边 → 左边

  • 左边 → 上边

这形成了一个循环替换,需要用一个临时变量保存第一个元素。

4. 分层旋转中的边界条件是什么?

  • 对于 n × n 矩阵,有 n//2 层需要旋转

  • 每一层从 firstlast-1(不包括最后一个元素)

  • 最后一个元素会在前一个元素的旋转中被处理

5. 如果 n 为奇数怎么办?

  • 中间的元素不需要旋转,保持原位

  • 无论是转置+翻转还是分圈旋转,都能正确处理奇数 n

七、复杂度分析

方法一(转置 + 翻转)

  • 时间复杂度:O(n²)

    • 转置:遍历大约 n²/2 个元素

    • 翻转:遍历 n 行,每行 n/2 次交换

    • 总操作次数:约 n²

  • 空间复杂度:O(1)

    • 只使用了常数个临时变量

方法二(分圈旋转)

  • 时间复杂度:O(n²)

    • 每个元素被访问一次

    • 总操作次数:n²

  • 空间复杂度:O(1)

    • 只使用了常数个临时变量

八、其他解法

解法一:直接位置映射法

python

复制代码
class Solution:
    def rotate(self, matrix: List[List[int]]) -> None:
        n = len(matrix)
        
        # 直接计算每个元素的新位置
        for i in range(n // 2):
            for j in range(i, n - 1 - i):
                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

解法二:使用额外空间(不符合要求但易于理解)

python

复制代码
class Solution:
    def rotate(self, matrix: List[List[int]]) -> None:
        n = len(matrix)
        # 创建临时矩阵
        rotated = [[0] * n for _ in range(n)]
        
        # 计算旋转后的位置
        for i in range(n):
            for j in range(n):
                rotated[j][n-1-i] = matrix[i][j]
        
        # 复制回原矩阵
        for i in range(n):
            for j in range(n):
                matrix[i][j] = rotated[i][j]

九、常见问题与解答

Q1: 如果我想逆时针旋转90度怎么办?

A1: 有两种方法:

  1. 先转置,再垂直翻转(而不是水平翻转)

  2. 先水平翻转,再转置

Q2: 这个方法适用于非方阵吗?

A2: 不适用。旋转90度的定义只对方阵有意义。对于非方阵,旋转后行列数会变化,需要新的矩阵。

Q3: 如果矩阵中有负数或0怎么办?

A3: 算法与元素值无关,只与位置有关。无论元素是什么值,算法都能正确旋转。

Q4: 如何测试旋转的正确性?

A4: 可以测试以下情况:

  • 1×1 矩阵

  • 2×2 矩阵

  • 3×3 矩阵(奇数)

  • 4×4 矩阵(偶数)

  • 包含各种值的矩阵

  • 旋转两次应得到旋转180度的结果

  • 旋转四次应回到原矩阵

Q5: 为什么不能使用额外矩阵?

A5: 题目要求原地旋转,这是对空间复杂度的要求。使用额外矩阵虽然简单,但空间复杂度为 O(n²)。

十、相关题目

1. LeetCode 1886. 判断矩阵经轮转后是否一致

python

复制代码
class Solution:
    def findRotation(self, mat: List[List[int]], target: List[List[int]]) -> bool:
        n = len(mat)
        
        # 四种可能的旋转角度:0, 90, 180, 270度
        for _ in range(4):
            if mat == target:
                return True
            # 旋转90度
            mat = [list(row) for row in zip(*mat[::-1])]
        
        return False

2. LeetCode 867. 转置矩阵

python

复制代码
class Solution:
    def transpose(self, matrix: List[List[int]]) -> List[List[int]]:
        m, n = len(matrix), len(matrix[0])
        
        # 创建转置矩阵
        transposed = [[0] * m for _ in range(n)]
        
        # 填充转置矩阵
        for i in range(m):
            for j in range(n):
                transposed[j][i] = matrix[i][j]
        
        return transposed

十一、总结

核心要点

  1. 数学原理:旋转90度 = 转置 + 水平翻转

  2. 原地操作:通过交换元素实现,不使用额外空间

  3. 边界处理:注意遍历范围和交换条件

算法步骤(转置 + 翻转法)

  1. 转置矩阵(行列互换),只遍历对角线下方元素

  2. 水平翻转每一行(每行反转)

时间复杂度与空间复杂度

  • 时间复杂度:O(n²),每个元素被访问一次

  • 空间复杂度:O(1),只使用常数个临时变量

适用场景

  • 图像处理中的旋转操作

  • 矩阵变换相关问题

  • 需要原地操作的数组/矩阵问题

扩展思考

矩阵旋转问题展示了如何通过基本操作(转置、翻转)组合实现复杂变换。这种思想可以应用于:

  • 其他角度的旋转(180度、270度)

  • 矩阵的对称变换

  • 图像处理中的各种几何变换

  • 线性代数中的基变换

掌握矩阵旋转的解法不仅能够解决这类特定问题,还能加深对矩阵操作和空间复杂度的理解,是面试中常见的算法题目。

相关推荐
好学且牛逼的马2 小时前
【Hot100|18-LeetCode 54. 螺旋矩阵】
算法·leetcode·矩阵
YuTaoShao2 小时前
【LeetCode 每日一题】3602. 十六进制和三十六进制转化——(解法二)手写进制转换
linux·python·leetcode
努力学算法的蒟蒻2 小时前
day70(1.29)——leetcode面试经典150
算法·leetcode·面试
源代码•宸2 小时前
Leetcode—144. 二叉树的前序遍历【简单】
经验分享·算法·leetcode·面试·职场和发展·golang·dfs
2401_841495643 小时前
【LeetCode刷题】二叉树的中序遍历
数据结构·python·算法·leetcode··递归·遍历
坚持不懈的大白3 小时前
leetcode学习笔记2
笔记·学习·leetcode
踩坑记录12 小时前
leetcode hot100 2.两数相加 链表 medium
leetcode·链表
历程里程碑14 小时前
滑动窗口---- 无重复字符的最长子串
java·数据结构·c++·python·算法·leetcode·django
TracyCoder12316 小时前
LeetCode Hot100(18/100)——160. 相交链表
算法·leetcode