22. 旋转图像
题目描述
给定一个 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]]
提示:
n == matrix.length == matrix[i].length
1 <= n <= 20
-1000 <= matrix[i][j] <= 1000
求解
(1)使用辅助矩阵
关键思路:matrixnew[col][n−row−1]=matrix[row][col]
js
var rotate = function(matrix) {
let m = matrix.length
// 用一个辅助矩阵
let temp = new Array(m).fill(0).map(() => new Array(m).fill(0))
// 第一行 变为 最后一列; 第二行变为 倒数第二列 。。。
for (let i = 0; i < m; i++) {
for (let j = 0; j < m; j++) {
temp[j][m - i - 1] = matrix[i][j]
}
}
for (let i = 0; i < m; i++) {
for (let j = 0; j < m; j++) {
matrix[i][j] = temp[i][j]
}
}
return matrix
};
(2)原地操作:
核心思路:水平翻转+主对角线翻转
js
var rotate = function(matrix) {
let m = matrix.length
// 翻转 代替 旋转
// 先水平翻转 + 再主对角线翻转
for (let i = 0; i < m / 2; i++) {
for (let j = 0; j < m; j++) {
[matrix[i][j], matrix[m - i - 1][j]] = [matrix[m - i - 1][j], matrix[i][j]]
}
}
for (let i = 0; i < m; i++) {
for (let j = i + 1; j < m; j++) {
[matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]]
}
}
return matrix
};
复习知识点
变量赋值
核心概念:值类型 vs. 引用类型,这是理解 JavaScript 赋值行为的基石。JavaScript 中的数据类型可以分为两大类,它们的赋值规则截然不同。
1. 值类型 (Value Types / Primitive Types)
-
特点:变量存储的是数据本身。
-
包括:
- String (字符串)
- Number (数字)
- Boolean (布尔值)
- null (空值)
- undefined (未定义)
- Symbol (ES6 新增)
- BigInt (ES10 新增)
-
赋值行为:传值 (Pass by Value)。
- 当你把一个值类型的变量赋值给另一个变量时,JavaScript 会创建这个值的一个副本,然后将副本赋给新变量 。此后,两个变量是完全独立的,修改其中一个不会影响另一个。
- 示例:
javascriptlet a = 10; let b = a; // 将 a 的值 10 复制一份,赋给 b b = 20; // 修改 b 的值 console.log(a); // 输出: 10 (a 的值不受影响) console.log(b); // 输出: 20
2. 引用类型 (Reference Types)
-
特点:变量存储的是数据在内存中的地址(引用 / 指针),而不是数据本身。
-
包括:
- Object (对象)
- Array (数组)
- Function (函数)
- 以及所有通过 new 关键字创建的对象(如 Date, RegExp 等)
-
赋值行为:传引用 (Pass by Reference)。
- 当你把一个引用类型的变量赋值给另一个变量时,JavaScript 复制的是这个变量存储的内存地址,然后将这个地址副本赋给新变量。结果是,两个变量指向了内存中的同一个对象。因此,通过任何一个变量修改对象的属性或内容,都会影响到另一个变量。
- 示例:
javascriptlet obj1 = { name: "张三" }; let obj2 = obj1; // 复制 obj1 存储的内存地址,obj2 也指向同一个对象 obj2.name = "李四"; // 通过 obj2 修改对象的属性 console.log(obj1.name); // 输出: "李四" (obj1 指向的对象也被修改了) console.log(obj2.name); // 输出: "李四" -
注意:这里的 "修改" 指的是修改对象内部的属性或元素。如果是直接让变量指向一个新的对象,则属于重新赋值,不会影响其他变量。
javascriptlet obj1 = { name: "张三" }; let obj2 = obj1; obj2 = { name: "王五" }; // obj2 现在指向一个全新的对象 console.log(obj1.name); // 输出: "张三" (obj1 不受影响) console.log(obj2.name); // 输出: "王五"
重要的延伸概念
1. 浅拷贝 (Shallow Copy)
-
对于引用类型,简单的赋值 (=) 是传引用。如果你想创建一个新的对象,但又想共享原始对象的部分数据,可以使用浅拷贝。
-
特点:创建一个新的对象,然后将原始对象的属性值(如果是值类型则复制值,如果是引用类型则复制引用)逐一赋给新对象。
-
方法:
- Object.assign({}, originalObj)
- 扩展运算符 { ...originalObj }
- Array.prototype.slice() 或 [...originalArr] (用于数组)
-
示例:
javascriptconst original = { a: 1, b: { c: 2 } // b 是一个引用类型 }; const shallowCopy = { ...original }; shallowCopy.a = 100; // 修改值类型属性,不影响原始对象 shallowCopy.b.c = 200; // 修改引用类型属性的内部值,会影响原始对象 console.log(original.a); // 输出: 1 console.log(original.b.c); // 输出: 200 (被影响了!)
2. 深拷贝 (Deep Copy)
-
特点:创建一个全新的、完全独立的对象,递归地复制原始对象及其所有嵌套的引用类型数据。
-
方法:
JSON.parse(JSON.stringify(originalObj))(简单但有局限性,不支持函数、undefined、Date 等)- 使用库函数,如 Lodash 的
_.cloneDeep()
-
手动实现递归拷贝函数
-
示例:
javascriptconst original = { a: 1, b: { c: 2 } }; const deepCopy = JSON.parse(JSON.stringify(original)); deepCopy.b.c = 200; // 修改深拷贝对象内部的嵌套对象 console.log(original.b.c); // 输出: 2 (原始对象不受影响) console.log(deepCopy.b.c); // 输出: 200
总结:
| 场景 | 操作 | 结果 | 说明 |
|---|---|---|---|
| 值类型赋值 | let x = 5; let y = x; | y 是 x 的值的副本。 | 修改 y 不会影响 x。 |
| 引用类型赋值 | let a = {x: 5}; let b = a; | b 和 a 指向同一个对象。 | 通过 b 修改对象属性会影响 a。 |
| 重新赋值 | let a = {x: 5}; let b = a; b = {x: 10}; | b 现在指向一个新对象。 | a 仍然指向原来的对象,不受影响。 |
| 浅拷贝 | let copy = { ...original }; | 创建新对象,但其引用类型属性仍指向原对象。 | 只深拷贝了一层。 |
| 深拷贝 | let deepCopy = JSON.parse(JSON.stringify(original)); | 创建一个完全独立的新对象,包括所有嵌套数据。 | 两个对象彻底无关。 |