hot100_旋转图像

旋转图像题解

hot100_旋转图像

题目

给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 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]]


题解

辅助数组

核心思想

图像顺时针旋转90度,其实有点像矩阵的转置,不过是把第一行放到最后一列,第二行放到倒数第二列,以此类推。

第一行:

[123∘∘∘∘∘∘]⇒旋转后[∘∘1∘∘2∘∘3]\begin{bmatrix} 1 & 2 & 3 \\ \circ & \circ & \circ \\ \circ & \circ & \circ \end{bmatrix} \xRightarrow{旋转后} \begin{bmatrix} \circ & \circ & 1 \\ \circ & \circ & 2 \\ \circ & \circ & 3 \end{bmatrix}

第二行:

[∘∘∘456∘∘∘]⇒旋转后[∘4∘∘5∘∘6∘]\begin{bmatrix} \circ & \circ & \circ \\ 4 & 5 & 6 \\ \circ & \circ & \circ \end{bmatrix} \xRightarrow{旋转后} \begin{bmatrix} \circ & 4 & \circ \\ \circ & 5 & \circ \\ \circ & 6 & \circ \end{bmatrix}

第三行:

[∘∘∘∘∘∘789]⇒旋转后[7∘∘8∘∘9∘∘]\begin{bmatrix} \circ & \circ & \circ \\ \circ & \circ & \circ \\ 7 & 8 & 9 \end{bmatrix} \xRightarrow{旋转后} \begin{bmatrix} 7 & \circ & \circ \\ 8 & \circ & \circ \\ 9 & \circ & \circ \end{bmatrix}

总结推导:

[a11a12⋯a1na21a22⋯a2n⋮⋮⋱⋮an1an2⋯ann]⇒旋转后[an1⋯a21a11an2⋯a22a12⋮⋱⋮⋮ann⋯a2na1n]\begin{bmatrix} a_{11} & a_{12} & \cdots & a_{1n} \\ a_{21} & a_{22} & \cdots & a_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{n1} & a_{n2} & \cdots & a_{nn} \end{bmatrix} \xRightarrow{旋转后} \begin{bmatrix} a_{n1} & \cdots & a_{21} & a_{11} \\ a_{n2} & \cdots & a_{22} & a_{12} \\ \vdots & \ddots & \vdots & \vdots \\ a_{nn} & \cdots & a_{2n} & a_{1n} \end{bmatrix}

翻译成代码:matrix[row][col]matrix[row][col] 的位置会变成matrixnew[col][n−row−1]matrix_{new}[col][n - row - 1]

代码
class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n = matrix.size();
        vector<vector<int>> matrixNew(n, vector<int> (n));
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                matrixNew[j][n - i - 1] = matrix[i][j];
            }
        }
        matrix = matrixNew;
    }
};

时间复杂度:O(n2)O(n^2),其中 nn 是矩阵的边长。 空间复杂度:O(n2)O(n^2),需要一个和 matrixmatrix 一样大的矩阵。

原地旋转

核心思想
1. 普通坐标系(数学 xy 坐标系)
数学坐标系:原点左下角,y↑向上增大\text{数学坐标系:原点左下角,}y\uparrow \text{向上增大} 矩阵数组坐标系:原点左上角,i↓向下增大\text{矩阵数组坐标系:原点左上角,}i\downarrow \text{向下增大}

同样一个屏幕上的点:

  • 数学坐标:y越大,点越靠上
  • 矩阵下标:i越大,点越靠下
{矩阵坐标系:i 向下增大数学笛卡尔坐标系:y 向上增大⇒竖直镜像翻转\begin{cases} \text{矩阵坐标系:}i \text{ 向下增大} \\ \text{数学笛卡尔坐标系:}y \text{ 向上增大} \end{cases} \Rightarrow \text{竖直镜像翻转}

矩阵左上角是原点(0,0)(0,0);数学坐标系左上角是(0,n−1)(0,n-1)。

y=(n−1)−iy=(n-1)-i 的来源

举 n=3n=3,n−1=2n-1=2:

矩阵行号i在矩阵中的位置想要得到的数学y
i=0最上面一行y=2
i=1中间行y=1
i=2最下面一行y=0

观察表格:

i=0→y=2i=0 \to y=2

i=1→y=1i=1 \to y=1

i=2→y=0i=2 \to y=0

可以看到:i+y=n−1\boldsymbol{i+y = n-1} 移项直接得到:

y=(n−1)−i\boldsymbol{y = (n-1)-i}

几何理解:上下镜像反射

一条线段,下标 0∼L0 \sim L,点 x 关于中线的镜像点:

x′=L−xx' = L - x

这里 L=n−1L = n-1,就是最大下标。

矩阵的行 i,以整个矩阵的上下边界做镜像,得到数学坐标的 y。

2. 坐标转换三步法(标准推导)

设矩阵阶数n,下标0∼n−10\sim n-1。 原矩阵位置:矩阵坐标 Pm=(i,j)P_m=(i,j)

1️⃣ 矩阵坐标 → 数学笛卡尔坐标(y 向上) 把矩阵向下的i翻转成向上的 y:

x=j,y=n−1−ix = j,\quad y = n-1-i

矩阵(i,j)(i,j) 对应数学点(x,y)=(j, n−1−i)(x,y)=(j,\ n-1-i)

2️⃣ 数学坐标系点顺时针旋转 90° 旋转公式 (x,y)→顺90°(y,−x)(x,y)\xrightarrow{\text{顺90°}} (y,-x) 代入得到旋转后的数学坐标(x1,y1)(x_1,y_1):

x1=y=n−1−i,y1=−x=−jx_1 = y = n-1-i,\quad y_1 = -x = -j

3️⃣ 把旋转后的数学坐标,转换回矩阵坐标(y 轴向下) 数学坐标(x1,y1)(x_1,y_1)变回矩阵(i′,j′)(i',j') 转换关系:

j′=x1,i′=n−1−y1j' = x_1,\quad i' = n-1-y_1

代入 x1=n−1−i, y1=−jx_1=n-1-i,\ y_1=-j:

j′=n−1−ii′=n−1−(−j)=j\begin{align*} j' &= n-1-i \\ i' &= n-1 - (-j) = j \end{align*}

得到旋转后的矩阵坐标:

(i′,j′)=(j, n−1−i)\boldsymbol{(i',j')=(j,\ n-1-i)}

✅这就是核心映射:矩阵中(i,j)(i,j)顺时针转 90 度后,落到位置 (j, n−1−i)\boldsymbol{(j,\ n-1-i)}。


{矩阵坐标(i,j)→转数学坐标{x=jy=n−1−i(x,y)→顺时针90°旋转(x1,y1)=(y,−x)(x1,y1)→转回矩阵坐标{j′=x1i′=n−1−y1⇒(i′,j′)=(j, n−1−i)\begin{cases} \text{矩阵坐标}(i,j)\xrightarrow{\text{转数学坐标}} \begin{cases} x=j\\ y=n-1-i \end{cases} \\[1em] (x,y)\xrightarrow{\text{顺时针90°旋转}} (x_1,y_1)=(y,-x) \\[1em] (x_1,y_1)\xrightarrow{\text{转回矩阵坐标}} \begin{cases} j'=x_1\\ i'=n-1-y_1 \end{cases} \end{cases} \Rightarrow \boldsymbol{(i',j')=(j,\ n-1-i)}
3. 得到完整 4 点闭环

已知变换函数 f(i,j)=(j, n−1−i)f(i,j) = \big(j,\ n-1-i\big)

  1. P0=(i,j)P_0=(i,j)
  2. P1=f(P0)=(j, n−1−i)P_1=f(P_0) =\big(j,\ n-1-i\big)
  3. P2=f(P1)=f(j,n−1−i)=(n−1−i, n−1−j)P_2=f(P_1) = f(j,n-1-i) = \big(n-1-i,\ n-1-j\big)
  4. P3=f(P2)=f(n−1−i,n−1−j)=(n−1−j, i)P_3=f(P_2) = f(n-1-i,n-1-j)=\big(n-1-j,\ i\big)
  5. P4=f(P3)=(i,j)=P0P_4=f(P_3)=(i,j)=P_0,回到起点,闭环。

(i,j)→(j,n−1−i)→(n−1−i,n−1−j)→(n−1−j,i)→(i,j)\boldsymbol{(i,j)\to (j,n-1-i)\to(n-1-i,n-1-j)\to(n-1-j,i)\to(i,j)}

实例验证 n=3

n=3,n−1=2n=3,n-1=2,取P0=(0,0)P_0=(0,0)

  • P0=(0,0)P_0=(0,0)
  • P1=f(0,0)=(0,2)P_1=f(0,0)=(0,2)
  • P2=f(0,2)=(2,2)P_2=f(0,2)=(2,2)
  • P3=f(2,2)=(2,0)P_3=f(2,2)=(2,0)
  • P4=f(2,0)=(0,0)P_4=f(2,0)=(0,0)

对应矩阵四个角:(0,0)→(0,2)→(2,2)→(2,0)→(0,0)(0,0)\to(0,2)\to(2,2)\to(2,0)\to(0,0),完全符合。

[123456789]\begin{bmatrix} \color{red}{1} & 2 & \color{blue}{3}\\ 4 & 5 & 6\\ \color{orange}{7} & 8 & \color{green}{9} \end{bmatrix}
4. 枚举哪些点

不能遍历全部矩阵,否则同一组 4 个点会被重复处理,覆盖已经交换好的值。

只枚举左上角子区域:

{0≤i<⌊n2⌋0≤j<⌈n2⌉\begin{cases} 0 \le i < \left\lfloor \dfrac{n}{2} \right\rfloor \\[0.8em] 0 \le j < \left\lceil \dfrac{n}{2} \right\rceil \end{cases} ⌈n2⌉=n+12(整数除法)\left\lceil \frac{n}{2} \right\rceil = \frac{n+1}{2}\quad(\text{整数除法})
  • n偶数:左上角 n2×n2\dfrac n2 \times \dfrac n2 的区域。
  • n奇数:包含中间列,矩阵中心点不会被枚举,无需处理。

图示:黄色为枚举起点

n=3:
🟨 🟨 ⬜
⬜ ⬜ ⬜
⬜ ⬜ ⬜

n=4:
🟨 🟨 ⬜ ⬜
🟨 🟨 ⬜ ⬜
⬜ ⬜ ⬜ ⬜
⬜ ⬜ ⬜ ⬜
代码
class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n = matrix.size();
        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 - 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;
            }
        }
    }
};

时间复杂度:O(n2)O(n^2),其中 nn 是矩阵的边长。 空间复杂度:O(1)O(1),原地旋转。

转置 + 每行反转

核心思想

矩阵顺时针旋转 90° = 主对角线转置,再每一行左右翻转。

本质:把旋转拆解成两个简单矩阵变换,避开四点轮换复杂的坐标闭环与循环边界。

  1. 主对角线转置

主对角线:左上到右下,i=ji=j。 转置操作:交换 matrix[i][j]↔matrix[j][i]\boldsymbol{matrix[i][j] \leftrightarrow matrix[j][i]}。

⚠️注意:只处理上三角区域 j>ij>i。 如果全部 i,j 循环,每一对元素会交换两次,变回原始矩阵。

示例 n=3n=3: 原矩阵

[123456789]⇒主对角线转置[147258369]\begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{bmatrix} \xRightarrow{\text{主对角线转置}} \begin{bmatrix} 1 & 4 & 7 \\ 2 & 5 & 8 \\ 3 & 6 & 9 \end{bmatrix}
  1. 每行左右原地反转

把转置完成后的矩阵,每一行做镜像左右翻转。

[147258369]⇒每行反转[741852963]\begin{bmatrix} 1 & 4 & 7 \\ 2 & 5 & 8 \\ 3 & 6 & 9 \end{bmatrix} \xRightarrow{\text{每行反转}} \begin{bmatrix} 7 & 4 & 1 \\ 8 & 5 & 2 \\ 9 & 6 & 3 \end{bmatrix}

得到顺时针旋转 90° 的结果。

为什么这两步组合等价顺时针 90°(简单推导)

设原矩阵元素 M[i][j]M[i][j]。

  1. 主对角线转置之后:M[i][j]M[i][j] 跑到 M[j][i]M[j][i]。
  2. 行反转:同一行中,列 j 映射到 n−1−jn-1-j。

合并两步:

  • 第一步转置:(i,j)⇒(j,i)(i,j) \Rightarrow (j,i)
  • 第二步行翻转:列下标 i⇒n−1−ii \Rightarrow n-1-i

最终位置:

(i,j)⇒(j, n−1−i)\boldsymbol{(i,j) \Rightarrow \big(j,\ n-1-i\big)}

(i,j)原→转置(j,i)→行反转(j, n−1−i)(i,j)_{\text{原}} \xrightarrow{\text{转置}} (j,i) \xrightarrow{\text{行反转}} \big(j,\ n-1-i\big)

✅和四点轮换的核心坐标映射公式完全一致。

也就是说:转置 + 行反转,本质就是间接实现了 (i,j)→(j,n−1−i)(i,j)\to(j,n-1-i) 的坐标变换。

代码
class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n = matrix.size();
        for(int i = 0; i < n; ++i){
            for(int j = i + 1; j < n; ++j){
                swap(matrix[i][j], matrix[j][i]);
            }
        }
        for(int i = 0; i < n; ++i){
            reverse(matrix[i].begin(), matrix[i].end());
        }
    }
};

时间复杂度:O(n2)O(n^2),每个元素访问常数次。 空间复杂度:O(1)O(1),原地操作。

三种算法对比

算法是否原地时间复杂度额外空间优点缺点适合场景
辅助矩阵拷贝❌O(n2)O(n^2)O(n2)O(n^2)逻辑最简单,零边界错误不满足原地要求理解坐标映射,教学演示
四点轮换交换✅O(n2)O(n^2)O(1)O(1)体现完整坐标变换原理循环边界易错,代码繁琐面试手撕、原理考察
转置 + 每行反转✅O(n2)O(n^2)O(1)O(1)代码简短,边界简单看不到旋转闭环,依赖 reverse笔试做题、快速 AC

48. 旋转图像 - 力扣(LeetCode)

chengzi