LeetCode 题解——旋转图像

大家好,我是前端西瓜哥。今天来做一道 medium 难度的 LeetCode 算法题:旋转图像。

废话不多说,我们直接看题目。

LeetCode 题解——旋转图像_时间复杂度

简单来说,就是要将一个从左往右从上到下的正方形矩阵,旋转 90 度,转换为从上往下从右往左的矩阵,且需要为原地算法,不使用额外的一个矩阵。

LeetCode 对应题目为:

  1. 旋转图像:https://leetcode-cn.com/problems/rotate-image/
  • 《程序员面试金典(第 6 版)》面试题 01.07. 旋转矩阵:https://leetcode-cn.com/problems/rotate-matrix-lcci/

额外用一个矩阵

虽然题目要求不使用另一个矩阵,但我还是试着写了个额外用一个矩阵的解法。

因为如果题目没有做限制,这种写法是最容易想到且实现简单。

function rotate(matrix: number[][]): void {
const n = matrix.length;
// 初始化
const tmpMatrix = new Array(n);
for (let i = 0; i < n; i++) {
tmpMatrix[i] = new Array(n);
}

// 旋转
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
tmpMatrix[j][n - 1 - i] = matrix[i][j]
}
}

// 拷贝回去(浅拷贝)
for (let i = 0; i < n; i++) {
matrix[i] = tmpMatrix[i];
}
}

首先是初始化一个大小和 matrix 一样的二维数组 tmpMatrix。

然后遍历 matrix 将其从上到下的每一行,对应到 ret 的从右往左的每一列里,对应关系为:​​tmpMatrix[j][n - 1 - i] = matrix[i][j]​​。

最后将 tmpMatrix 拷贝回 matrix。

就是这么简单。

时间复杂度 ​​O(n^2)​​​,空间复杂度 ​​O(n^2)​

从外到内一层层旋转

还有一种就是从最外层到最内层,一层层做旋转,就像剥洋葱一样。

function rotate(matrix: number[][]): void {
const n = matrix.length;

for (let i = 0, len = Math.floor(n / 2); i < len; i++) { // 一共有 n/2
const end = n - i - 1; // 结束的索引
for (let j = i; j < end - i; j++) {
swap(matrix, i, j, j, end - i);
swap(matrix, i, j, end - i, end - j);
swap(matrix, i, j, end - j, i);
}
}
};

function swap(matrix: number[][], a: number, b: number, c: number, d: number) {
let tmp = matrix[a][b];
matrix[a][b] = matrix[c][d];
matrix[c][d] = tmp;
// 或 js 特有的交换写法
// [matrix[a][b], matrix[c][d]] = [matrix[c][d], matrix[a][b]];
}

LeetCode 题解——旋转图像_i++_02


我们每次需要将上图中的这四个块做一个顺时针的依次交换。这里的难点在于找出四个点的对应关系。要找出这个规律,你要注意 i, j 的流向。对于特定的一圈,它的 i 是不变的,j 则是递增的。

假设我们用 ​​matrix[x][y]​​​ 代表一个点,​​end​​ 表示结束的索引的位置。

图中黄色部分从左往右,x 不变,y 增大,所以是 ​​(i, j)​​;

橙色部分从上到下 x 增大,y 不变,但它在最右侧,所以是 ​​(j, end - i)​​;

红色部分从右到左 x 变小,y 不变,但它在最下边,所以是 ​​(end - i, end - j)​​;

粉色部分从下往上 x 边小,y 不变且值等于 i,所以是 ​​(end - j, i)​​;

时间复杂度 ​​O(n^2)​​​,空间复杂度 ​​O(1)​​。

对角线交换 + 左右交换(逆转)

这个实现就简单多了,其实就是将原本从左往右然后从上往下的顺序,先转换为从上往下然后从左往右的顺序,最后来一个倒转,变成从上往下然后从右往左。

LeetCode 题解——旋转图像_i++_03

function rotate(matrix: number[][]): void {
// 对角线交换
const n = matrix.length;
for (let i = 0; i < n; i++) {
for (let j = i + 1; j < n; j++) {
let tmp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = tmp;
}
}

// 左右交换
for (let i = 0; i < n; i++) {
matrix[i].reverse();
}
};

时间复杂度 ​​O(n^2)​​​,空间复杂度 ​​O(1)​​。

总结

第一个解法是没有思路时,通过尝试解除不使用额外矩阵的限制来实现的,如果面试不做空间复杂度的限制,可以考虑用这个解法。

第二个解法则是从最外层到最内层一层层旋转的思路,这个思路是从 “旋转” 这个角度思考,但实现上四个点的对应关系很容易写错,慎用。

面试时遇到这道题建议用第三个解法,也就是最后一个解法,相对较好理解,也不容易写错。