二维数组按顺时针螺旋顺序

28 阅读4分钟

前端写一个函数,接收一个二维数组按从左到右,从上到下,顺时针解成一维数组,例如接收[ [1,2,3,4], [12,13,14,5], [11,16,15,6], [10,9,8,7] ] 输出 [1, 2, 3, 4,5,6,7,8,9,10,11,12,13,14,15,16]

前端解决二维数组按顺时针螺旋顺序解成一维数组的问题,是一个经典的矩阵遍历问题。我们可以使用“边界收缩”的方法来解决。

问题描述

给定一个二维数组(矩阵),按照从左到右、从上到下、顺时针的顺序遍历所有元素,并将它们放入一个一维数组中。

解决思路 (边界收缩法)

我们可以使用四个变量来表示当前螺旋遍历的边界:

  • top: 当前层的上边界行索引
  • bottom: 当前层的下边界行索引
  • left: 当前层的左边界列索引
  • right: 当前层的右边界列索引

每次循环,我们按照顺时针方向遍历当前层的四个边,并相应地收缩边界:

  1. 从左到右遍历上边: 遍历 matrix[top][left]matrix[top][right]。遍历完成后,top 边界向下收缩,即 top++

  2. 从上到下遍历右边: 遍历 matrix[top][right]matrix[bottom][right]。遍历完成后,right 边界向左收缩,即 right--

  3. 从右到左遍历下边: 遍历 matrix[bottom][right]matrix[bottom][left]。遍历完成后,bottom 边界向上收缩,即 bottom--

    • 重要判断: 在遍历下边之前,需要检查 top <= bottom。如果 top > bottom,说明只剩下一行或已经没有行可以遍历了,避免重复遍历或越界。
  4. 从下到上遍历左边: 遍历 matrix[bottom][left]matrix[top][left]。遍历完成后,left 边界向右收缩,即 left++

    • 重要判断: 在遍历左边之前,需要检查 left <= right。如果 left > right,说明只剩下一列或已经没有列可以遍历了,避免重复遍历或越界。

重复以上步骤,直到 top > bottomleft > right,表示所有元素都已遍历完毕。

代码实现

/**
 * 将二维数组按顺时针螺旋顺序解成一维数组
 * @param {number[][]} matrix - 输入的二维数组
 * @returns {number[]} - 螺旋遍历后的一维数组
 */
function spiralUnwrap(matrix) {
    const result = [];

    // 处理空矩阵或空行的情况
    if (!matrix || matrix.length === 0 || matrix[0].length === 0) {
        return result;
    }

    let top = 0; // 上边界行索引
    let bottom = matrix.length - 1; // 下边界行索引
    let left = 0; // 左边界列索引
    let right = matrix[0].length - 1; // 右边界列索引

    // 当边界有效时循环
    while (top <= bottom && left <= right) {
        // 1. 从左到右遍历上边 (top row)
        for (let i = left; i <= right; i++) {
            result.push(matrix[top][i]);
        }
        top++; // 上边界向下收缩

        // 2. 从上到下遍历右边 (right column)
        for (let i = top; i <= bottom; i++) {
            result.push(matrix[i][right]);
        }
        right--; // 右边界向左收缩

        // 3. 从右到左遍历下边 (bottom row)
        // 必须确保还有行可以遍历,防止单行矩阵重复遍历
        if (top <= bottom) {
            for (let i = right; i >= left; i--) {
                result.push(matrix[bottom][i]);
            }
            bottom--; // 下边界向上收缩
        }

        // 4. 从下到上遍历左边 (left column)
        // 必须确保还有列可以遍历,防止单列矩阵重复遍历
        if (left <= right) {
            for (let i = bottom; i >= top; i--) {
                result.push(matrix[i][left]);
            }
            left++; // 左边界向右收缩
        }
    }

    return result;
}

// 示例测试
const matrix1 = [
    [1, 2, 3, 4],
    [12, 13, 14, 5],
    [11, 16, 15, 6],
    [10, 9, 8, 7]
];
console.log("输入矩阵:", matrix1);
console.log("输出数组:", spiralUnwrap(matrix1));
// 期望输出: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]

const matrix2 = [
    [1, 2, 3],
    [8, 9, 4],
    [7, 6, 5]
];
console.log("输入矩阵:", matrix2);
console.log("输出数组:", spiralUnwrap(matrix2));
// 期望输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]

const matrix3 = [
    [1, 2, 3, 4, 5]
];
console.log("输入矩阵:", matrix3);
console.log("输出数组:", spiralUnwrap(matrix3));
// 期望输出: [1, 2, 3, 4, 5]

const matrix4 = [
    [1],
    [2],
    [3],
    [4],
    [5]
];
console.log("输入矩阵:", matrix4);
console.log("输出数组:", spiralUnwrap(matrix4));
// 期望输出: [1, 2, 3, 4, 5]

const matrix5 = [
    [1]
];
console.log("输入矩阵:", matrix5);
console.log("输出数组:", spiralUnwrap(matrix5));
// 期望输出: [1]

const matrix6 = [];
console.log("输入矩阵:", matrix6);
console.log("输出数组:", spiralUnwrap(matrix6));
// 期望输出: []

const matrix7 = [
    []
];
console.log("输入矩阵:", matrix7);
console.log("输出数组:", spiralUnwrap(matrix7));
// 期望输出: []

复杂度分析

  • 时间复杂度: O(M * N),其中 M 是矩阵的行数,N 是矩阵的列数。因为算法会精确地访问矩阵中的每一个元素一次。
  • 空间复杂度: O(M * N),用于存储结果一维数组。如果允许修改原矩阵,并且不需要返回新的数组,那么辅助空间复杂度可以是 O(1)。