54. 螺旋矩阵:边界维护,不断更新
- 定义初始的上下左右边界
top、bottom、left、right;定义初始遍历的个数num = 1, 迭代终止值是m * n。 - 当
num <= m * n时,始终按照从左到右、从上到下、从右到左、从下到上 填入顺序循环,每次填入后:- 执行 num++,更新 res
- 更新边界
const spiralOrder = function(matrix) {
let m = matrix.length; // 行数
let n = matrix[0].length; // 列数
let total = m * n; // 矩阵元素总数
let num = 1; // 迭代值初始化
const res = [];
let left = 0, top = 0; // 边界初始化
let right = n - 1, bottom = m - 1;
while (num <= total) {
for (let i = left; i <= right && num <= total; i++) { // 从左到右,注意双重判断!!
res.push(matrix[top][i]);
num++;
}
top++;
for (let i = top; i <= bottom && num <= total; i++) { // 从上到下
res.push(matrix[i][right]);
num++;
}
right--;
for (let i = right; i >= left && num <= total; i--) { // 从右向左
res.push(matrix[bottom][i]);
num++;
}
bottom--;
for (let i = bottom; i >= top && num <= total; i--) { // 从下向上
res.push(matrix[i][left]);
num++;
}
left++;
}
return res;
};
59. 螺旋矩阵II:初始化二维数组
思路和54. 螺旋矩阵一样,但多了一步初始化二维数组,然后继续顺时针填充即可。
const generateMatrix = function(n) {
let total = n * n;
let num = 1;
const matrix = new Array(n).fill(0).map(() => new Array(n).fill(0)); // 初始化就赋值,否则会报错
let left = 0, top = 0;
let right = n - 1, bottom = n - 1;
while (num <= total) {
for (let i = left; i <= right && num <= total; i++) {
matrix[top][i] = num;
num++;
}
top++;
for (let i = top; i <= bottom && num <= total; i++) {
matrix[i][right] = num;
num++;
}
right--;
for (let i = right; i >= left && num <= total; i--) { // 注意 i--
matrix[bottom][i] = num;
num++;
}
bottom--;
for (let i = bottom; i >= top && num <= total; i--) { // 注意 i--
matrix[i][left] = num;
num++;
}
left++;
}
return matrix;
};
48. 旋转图像:两次翻转,推荐用主对角线
在水平或竖直方向上的翻转问题,循环遍历的实际上就是数组的一半元素!
// 沿中心水平线翻转
for (let i = 0; i < Math.floor(n / 2); i++) {
for (let j = 0; j < n; j++) {
[mat[i][j], mat[n - 1 - i][j]] = [mat[n - 1 - i][j], mat[i][j]];
}
}
// 沿中心竖直线翻转
for (let i = 0; i < n; i++) {
for (let j = 0; j < Math.floor(n / 2); j++) {
[mat[i][j], mat[i][n - 1 - j]] = [mat[i][n - 1 - j], mat[i][j]];
}
}
// 沿"左上-右下"对角线翻转,如果涉及对角线,尽量选择左上-右下这一条,方便理解!
for (let i = 0; i < n; i++) {
for (let j = 0; j < i; j++) { // 注意
[mat[i][j], mat[j][i]] = [mat[j][i], mat[i][j]];
}
}
找规律发现,先以 「左上-右下对角线」 为轴作翻转,再以 「中心的竖线」 为轴作翻转,就可以实现将图像顺时针旋转90度的效果。
(或者先水平反转再对角线翻转都可以)
const rotate = function(matrix) {
const n = matrix.length;
for (let i = 0; i < n; i++) { // 先沿主对角线翻转
for (let j = 0; j < i; j++) { // 注意,刚好斜对角一半
[matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]];
}
}
for (let i = 0; i < n; i++) { // 再沿竖的中心线翻转
for (let j = 0; j < Math.floor(n / 2); j++) { // 注意,刚好一半
[matrix[i][j], matrix[i][n - j - 1]] = [matrix[i][n - j - 1], matrix[i][j]];
}
}
return matrix;
};
时间复杂度:O(n^2)。
空间复杂度:O(1)。 不占用额外空间
498. 对角线遍历:需要记规律
该题的重点就在于 「找规律」 和 「边界情况」。
- 找规律: 无论是左上还是右下的遍历方式,都不会改变当前 (行 + 列)的和 ,并且如果和是偶数,就是右上;和是奇数就是左上。
- 边界情况:
- 右上:
x-- && y++- if 当遍历到最后一列时,执行
x++ 而y不用变, - else if当遍历到第一行时,执行
y++ 而x不用变, - else 其他情况,则
x--; y++继续遍历。
- if 当遍历到最后一列时,执行
- 左下:
x++ && y--- if 当遍历到最后一行时,执行
y++ 而x不用变, - else if 当遍历到第一列时,执行
x++ 而y不用变, - else 其他情况,则
x++; y--继续遍历。
- if 当遍历到最后一行时,执行
- 注意:先判断后到达的临界条件。
- 右上方向时,因为可能会经过右上角,也就是同时满足 第一行和最后一列 的条件,因此后续的判断中
if和else if顺序非常重要。也就是要先判断是否为最后一列。 - 同理,左下方向时可能会经过左下角,需要先判断是否为最后一行。
- 右上方向时,因为可能会经过右上角,也就是同时满足 第一行和最后一列 的条件,因此后续的判断中
// 先填充值,再移动遍历!
const findDiagonalOrder = function(mat) {
const m = mat.length, n = mat[0].length; // 行数、列数
let x = 0, y = 0; // 初始坐标
const total = m * n;
let num = 1; // 迭代值初始化
const res = [];
while (num <= total) {
res.push(mat[x][y]); // 注意
num++;
// 注意 if -- else if -- else 结构
if ((x + y) % 2 === 0) { // 右上方向
if (y == (n - 1)) x++; // 最后一列
else if (x == 0) y++; // 第一行
else { // 正常右上
x--;
y++;
}
} else { // 左下方向
if (x == (m - 1)) y++; // 最后一行
else if (y == 0) x++; // 第一列
else { // 正常左下
x++;
y--;
}
}
}
return res;
}