小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
题目
给定一个 _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]]
示例 3:
**输入:**matrix = [[1]] 输出:[[1]]
示例 4:
**输入:**matrix = [[1,2],[3,4]] 输出:[[3,1],[4,2]]
提示:
matrix.length == nmatrix[i].length == n1 <= n <= 20-1000 <= matrix[i][j] <= 1000
思路
本文尝试倒序、顺序、层次、螺旋遍历和翻转矩阵,测试解构赋值交换数组元素的性能
一 倒序遍历 · 深拷贝:新 → 原
- 深拷贝原矩阵。任意顺序
遍历矩阵,坐标[i, j]= 原坐标[n - 1 - j][i]值
代码
Array.from + slice
var rotate = function (matrix) {
let n = matrix.length, m = Array.from({length: n}, (_, i) => matrix[i].slice())
for (let i = n; i--;)
for (let j = n; j--;)
matrix[i][j] = m[n - 1 - j][i]
};
JSON.stringify + JSON.parse
var rotate = function (matrix) {
let n = matrix.length, m = JSON.parse(JSON.stringify(matrix))
for (let i = n; i--;)
for (let j = n; j--;)
matrix[i][j] = m[n - 1 - j][i]
};
二 顺序遍历 · 拷贝:原 → 新 → 原
解题思路
- 任意顺序
遍历矩阵,新矩阵[j, n - 1 - j]= 原矩阵坐标[i, j]值
代码
var rotate = function(matrix) {
let n = matrix.length, m = Array.from({length: n}, _ => Array(n))
for(let i = 0; i < n; i++)
for(let j = 0; j < n; j++)
m[j][n - 1 - i] = matrix[i][j]
for(let i = 0; i < n; i++)
for(let j = 0; j < n; j++)
matrix[i][j] = m[i][j]
};
三 倒序垂直扫描 · 队列
解题思路
- 来自时间百分百,用队列解决,思路简单。
深蓝→浅蓝表示遍历顺序,下同
代码
var rotate = function(matrix) {
let n = matrix.length, queue = []
for(let i = 0; i < n; i++)
for(let j = 0; j < n; j++)
queue.push(matrix[i][j])
for(let j = n; j--;)
for(let i = 0; i < n; i++)
matrix[i][j] = queue.shift()
};
四 层次遍历 · 旋转
解题思路
- 每旋转1格,
上右下左四格值,存1换4 - 第
0层:0~n - 1- 第
1层:1~n - 1 - 1- ...
- 直到第
n / 2取整层:i~n - 1 - i
- 第
代码
var rotate = function(matrix) {
let n = matrix.length
for(let i = 0; i < n >> 1; i++)
for(let j = i; j < n - 1 - i; j++) {
let tmp = 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] = tmp
}
};
五 翻转
解题思路
垂直翻转→ 以n / 2取整为界,坐标[i, j]垂直翻转[i, n - 1 -j]对角线翻转→ 以右上到左下为界,坐标[i, j]对角线翻转[n - 1 - j, n - 1 - i]
代码
解构赋值
var rotate = function(matrix) {
let n = matrix.length
for(let i = 0; i < n; i++)
for(let j = 0; j < n >> 1; j++)
[matrix[i][n - 1 - j], matrix[i][j]] = [matrix[i][j], matrix[i][n - 1 - j]]
for(let i = 0; i < n - 1; i++)
for(let j = 0; j < n - 1 - i; j++)
[matrix[n - 1 - j][n - 1 - i], matrix[i][j]] = [matrix[i][j], matrix[n - 1 - j][n - 1 - i]]
};
中间变量
var rotate = function(matrix) {
let n = matrix.length, tmp, i, j
for(i = 0; i < n; i++)
for(j = 0; j < n >> 1; j++) {
tmp = matrix[i][j]
matrix[i][j] = matrix[i][n - 1 - j]
matrix[i][n - 1 - j] = tmp
}
for(i = 0; i < n - 1; i++)
for(j = 0; j < n - 1 - i; j++) {
tmp = matrix[i][j]
matrix[i][j] = matrix[n - 1 - j][n - 1 - i]
matrix[n - 1 - j][n - 1 - i] = tmp
}
};
六 螺旋遍历 · 队列
解题思路
螺旋遍历矩阵,方向d→↓←↑循环- 初始边界
b[上,右,左,下] = [1, n - 1, 0, n - 1] - 初始位置
i = 0j = 0,遍历格数k = 0,直到k = n * n - 1所有格遍历完→j++到右边界转↓,i++,右边界收缩-1↓i++到下边界转←,j--,下边界收缩-1←j--到左边界转↑,i--,左边界收缩+1↑i--到上边界转→,j++,上边界收缩+1
- 队列
queue放入遍历过的值→留空。到右边界时,弹出赋值↑弹出赋值←弹出赋值 到左边界,如[[1, 2], [3, 4]],判断到3已到头,填空↑弹出赋值 到上边界,如[[1, 2, 3],[4, 5, 6],[7, 8, 9]],到5已到头,填空
代码
碰撞转向
var rotate = function(matrix) {
let n = matrix.length, queue = []
let k = -1, i = 0, j = 0, b = [1, n - 1, 0, n - 1], d = '→'
while(++k < n * n) {
queue.push(matrix[i][j])
switch(d) {
case '→':
if (j < b[1]) j++
else {
matrix[i][j] = queue.shift()
i++
d = '↓'
b[1]--
}
break
case '↓':
matrix[i][j] = queue.shift()
if (i < b[3]) i++
else {
j--
d = '←'
b[3]--
}
break
case '←':
matrix[i][j] = queue.shift()
if (j > b[2]) j--
else {
if (k + 1 === n * n) {
for(let p = j; p <= b[1]; p++)
matrix[b[0] - 1][p] = queue.shift()
}
i--
d = '↑'
b[2]++
}
break
case '↑':
matrix[i][j] = queue.shift()
if (i > b[0]) i--
else {
for(let p = j; p <= b[1]; p++)
matrix[b[0] - 1][p] = queue.shift()
j++
d = '→'
b[0]++
}
}
}
};
循环
var rotate = function(matrix) {
const n = matrix.length, queue = []
let t = 0, r = n - 1, l = 0, b = n - 1, k = 0, i // 边界:t上 r右 l左 b下
while(k < n * n){
for(i = l; i <= r; i++) { // →
k++
queue.push(matrix[t][i])
if (i === r) matrix[t][i] = queue.shift()
}
t++
for(i = t; i <= b; i++) { // ↓
k++
queue.push(matrix[i][r])
matrix[i][r] = queue.shift()
}
r--
for(i = r; i >= l; i--) { // ←
k++
queue.push(matrix[b][i])
matrix[b][i] = queue.shift()
if (i === l && k === n * n)
while(queue.length)
matrix[t - 1][l++] = queue.shift()
}
b--
for(i = b; i >= t; i--) { // ↑
k++
queue.push(matrix[i][l])
matrix[i][l] = queue.shift()
if (i === t) {
let _l = l
while(queue.length)
matrix[t - 1][_l++] = queue.shift()
}
}
l++
}
return queue
}
排行
100 * 100矩阵100组,值在安全整数范围随机,每解法求解100次,每秒操作数
遍历矩阵:同构数组在内存连续,水平扫描缓存命中率 > 垂直扫描。矩阵越大,性能差距越明显
深拷贝矩阵:浅拷贝其中每个数组即可。JSON适合层多无function/undefined/Symbol/环对象
根据规范:解构赋值需要从被解构对象中(数组,对象)获取迭代器,迭代赋值
除Safari某些版本外,Firefox及Chrome都依规范实现,解构赋值速度 < 索引取值
正如使用异或交换变量,某些环境下不能真正节省内存一样,获取迭代器赋值开销 > 变量
在交换数组元素而不是变量时,性能差距更甚。所以对于JS,临时变量依然较好选择