NO.48 旋转图像 —— LeetCode 热题 100 面试导向深度解析

题目

给定一个 ( n \times n ) 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。

你必须 在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。

示例 1:

lua 复制代码
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]

示例 2:

lua 复制代码
输入: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]]

一、读题

一句话复述 :将一个 ( n \times n ) 的方阵原地顺时针旋转 90°,不能借助额外矩阵。

关键约束高亮:

  • ( n \times n ) 方阵(行数 = 列数),这是旋转能简化的前提;
  • 原地修改 (Do not return anything),意味着空间复杂度必须为 ( O(1) )(不计递归栈);
  • 矩阵元素类型为整数,无特殊范围限制。

二、前置知识

序号 知识点 说明
1 矩阵转置 沿主对角线(左上→右下)翻转,即 matrix[i][j] ↔ matrix[j][i]
2 水平翻转(行内逆序) 每行左右对称交换,即 matrix[i][j] ↔ matrix[i][n-1-j]
3 原地交换技巧 利用 ES6 解构赋值 [a, b] = [b, a] 无需临时变量,简洁且安全

非常见 API 表格:

API 作用 参数 返回值 使用场景
Math.floor(n / 2) 向下取整 数值 整数 行内翻转时确定只需遍历前半列
解构赋值 [a, b] = [b, a] 交换两个变量值 左右均为数组/类数组 undefined 原地交换矩阵元素,无需 temp

三、思路推导(核心)

暴力解(不满足题意)

最容易想到的是新建矩阵 :观察可知,旋转后 new[j][n-1-i] = old[i][j],即第 i 行第 j 列的元素会跑到第 j 行倒数第 i 列。

javascript 复制代码
var rotate = function(matrix) {
    const n = matrix.length;
    const newMatrix = Array.from({ length: n }, () => Array(n).fill(0));
    for (let i = 0; i < n; i++) {
        for (let j = 0; j < n; j++) {
            newMatrix[j][n - 1 - i] = matrix[i][j];
        }
    }
    // 拷贝回去(但这不算严格原地,且额外 O(n^2) 空间)
    for (let i = 0; i < n; i++) {
        for (let j = 0; j < n; j++) {
            matrix[i][j] = newMatrix[i][j];
        }
    }
};
  • 时间复杂度:( O(n^2) )(两轮遍历,常数倍忽略);
  • 空间复杂度:( O(n^2) )(额外矩阵)。

代入最大数据量(LeetCode 通常 ( n \le 20 ) 或更大),若 ( n=1000 ),则额外需要 100 万个元素的存储空间,内存可能不足,且违背题意。


优化动机 ------ 为什么能想到"转置 + 水平翻转"?

思维演进路径:

  1. 观察旋转前后坐标变化 :(i, j) → (j, n-1-i)。
  2. 拆解这个变换 :能否分解为两个更简单的操作?
    • 先做转置 :(i, j) → (j, i);
    • 再做水平翻转 :(j, i) → (j, n-1-i)。
    • 两步合起来正好是 (i, j) → (j, n-1-i)。
  3. 为什么这样想 :因为转置和翻转都是原地可完成的操作,且逻辑简单------转置只需遍历对角线一侧,翻转只需每行对折交换。这种"将复杂变换拆成两个已知简单操作"是算法设计的常见套路。

核心几何直觉:顺时针旋转 90° = 沿主对角线翻折 + 左右翻折(或者先上下翻折再转置,两种等价)。


手把手模拟(以 3×3 为例)

矩阵初始:

步骤 操作 矩阵状态
0 原始 \[1,2,3,4,5,6,7,8,9]
1 转置:交换 (0,1)↔(1,0) \[1,4,3,2,5,6,7,8,9]
2 转置:交换 (0,2)↔(2,0) \[1,4,7,2,5,6,3,8,9]
3 转置:交换 (1,2)↔(2,1) \[1,4,7,2,5,8,3,6,9]
4 水平翻转第 0 行:交换 (0,0)↔(0,2) \[7,4,1,2,5,8,3,6,9]
5 水平翻转第 1 行:交换 (1,0)↔(1,2) \[7,4,1,8,5,2,3,6,9]
6 水平翻转第 2 行:交换 (2,0)↔(2,2) \[7,4,1,8,5,2,9,6,3] ✅

四、代码与避坑

完整代码

javascript 复制代码
/**
 * @param {number[][]} matrix
 * @return {void} Do not return anything, modify matrix in-place instead.
 */
var rotate = function(matrix) {
    const n = matrix.length;

    // 1. 转置(沿主对角线翻转)
    // 为什么 j 从 i+1 开始?因为只需遍历上三角,避免重复交换
    for (let i = 0; i < n; i++) {
        for (let j = i + 1; j < n; j++) {
            [matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]];
        }
    }

    // 2. 每行左右翻转
    // 为什么 j < Math.floor(n / 2)?因为只需遍历每行前半部分,避免重复交换
    for (let i = 0; i < n; i++) {
        for (let j = 0; j < Math.floor(n / 2); j++) {
            [matrix[i][j], matrix[i][n - 1 - j]] = [matrix[i][n - 1 - j], matrix[i][j]];
        }
    }
};

边界攻防表

边界场景 ❌ 错误写法 ✅ 正确写法 原因
n = 1(单元素矩阵) 内外层循环正常执行,j < n 进入转置,j < Math.floor(n/2) 不执行 转置时 j = i+1 = 1,内层不执行;水平翻转 j < 0 不执行 对角线无元素可交换,行内无需翻转
n = 2(最小非平凡) 转置时 j 从 i+1 开始,正确交换一次;水平翻转 j < 1 交换一次 正确 注意双重循环的边界条件
偶数 n(如 n=4) 水平翻转时 j < n/2 即 j < 2,交换 (0,3) 和 (1,2),正确覆盖全部 正确 偶数行中心是两个元素,需交换 n/2 对

五、举一反三

面试追问

  1. 逆时针旋转 90° 怎么做?

    → 先转置,再上下翻转 (每列上下交换),即 (i,j) → (n-1-j, i)。

  2. 旋转 180° 怎么做?

    → 先上下翻转,再左右翻转;或者直接 (i,j) ↔ (n-1-i, n-1-j) 原地交换,只需遍历四分之一矩阵。

  3. 如果矩阵不是方阵(( m \times n )) ,能否原地旋转?

    → 不能严格原地,因为旋转后维度变为 ( n \times m ),形状改变,必须借助新矩阵。

  4. 如果要求旋转后输出新的矩阵,但原矩阵保留 (非原地)?

    → 直接用暴力解的新建矩阵即可,代码更简单,无需转置翻转。

相似题型

题目 差异点 解法变化
LeetCode 54. 螺旋矩阵 按螺旋顺序遍历输出,不需要修改原矩阵 用四个边界指针收缩,属于模拟题
LeetCode 59. 螺旋矩阵 II 生成一个 ( n \times n ) 的螺旋矩阵 按顺序填充数字,同样用边界指针
剑指 Offer 29. 顺时针打印矩阵 同 54 题,但要求返回一维数组 边界模拟,注意空矩阵处理

核心思路迁移:矩阵变换类问题,优先考虑"分解为转置 + 翻转"的组合,因为这两种操作原地成本低,易于实现。


一句话总结 :顺时针旋转 90° = 转置 + 水平翻转,原地完成,时间复杂度 ( O(n^2) ),空间复杂度 ( O(1) )。遇到类似矩阵变换,先想能否拆解为简单操作的组合。

相关推荐
架构技术专栏38 分钟前
学习率:一次参数更新,到底应该改多少
面试
架构技术专栏1 小时前
AI Agent 框架怎么选:从一次制度查询拆出技术边界
后端·面试
可乐鸡翅yeah_1 小时前
FFmpeg 生成 HLS 独立分片 independent‑segments 参数到底有什么用
javascript·ffmpeg·音视频·safari·m3u8
福兮说1 小时前
canvas 旋转图片的六个坑:四角被裁、Math.ceil 多出 1px 黑边、JPG 角发黑、翻转方向反了
前端·javascript·图像处理·canvas
念何架构之路2 小时前
zap采样器与性能优化内幕
算法·性能优化·哈希算法
Ai-_Man2 小时前
您您这可以把Grok的多个会话比如说。左侧的多个会话一次性导出吗?不是单条会话里面的多次会对话。AI导出鸭
javascript·人工智能·ai·小程序·电脑
liangshanbo12153 小时前
JavaScript 异步核心主线
javascript·事件循环·异步操作
晒太羊的猫3 小时前
hot100动态规划刷题
算法·动态规划
weixin_531094694 小时前
heap_4内存管理
java·开发语言·算法
余额瞒着我当琳4 小时前
红黑树的实现--红黑树的概念、规则、效率、结构、旋转、查找、验证
数据结构·c++·算法