【一天一大 lee】顺时针打印矩阵 (难度:中等) - Day20200605

181 阅读2分钟

题目(难度:中等):

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

示例

  • 示例 1
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
  • 示例 2
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
  • 限制:

    1. 0 <= matrix.length <= 100
    2. 0 <= matrix[i].length <= 100

抛砖引玉

img

  • 变量声明:

    • layer:循环的圈数从 0 开始 - 可做参数数组的索引(一个:完整的 →↓←↑ 算一圈),最大值 layer < Len / 2
    • Len:参数数组长度(可以不声明每次使用从新计算)
    • maxLen:参数数组中字段数量(用于判断循环终止条件-简单粗暴)
  • 判断的节点(循环变向的条件):

    • →:从当前圈中 matrix 未循环的首个子集 里的首个未推送项到末个未推送项(因为每次循环中 ↓↑ 都会个推送一个,则首项就是这行的第 layer 项、末项倒数第 layer)
    • ↓:从当前圈中 matrix 从第 layer+1(第 layer 个已经在 → 中推送)个子集的末个未推送项
    • ←:从当前圈中 matrix 未循环的末个子集 里的末项未推送项到首个未推送项
    • ↑:从当前圈中 matrix 从第 layer+1(第 layer 个已经在 → 中推送)个子集的首个未推送项

img

/**
 * @param {number[][]} matrix
 * @return {number[]}
 */
var spiralOrder = function (matrix) {
  var layer = 0,
    _result = [],
    Len = matrix.length,
    maxLen = Len
  if (Len === 0) return []

  while (layer < Len / 2) {
    maxLen = Len * matrix[layer].length
    // →
    for (var index = layer; index < matrix[layer].length - layer; index++) {
      _result[_result.length] = matrix[layer][index]
    }
    // ↓
    for (
      var index = layer + 1;
      index < Len - layer && _result.length < maxLen;
      index++
    ) {
      _result[_result.length] = matrix[index][matrix[index].length - 1 - layer]
    }
    // ←
    for (
      var index = matrix[Len - 1 - layer].length - 2 - layer;
      index > layer && _result.length < maxLen;
      index--
    ) {
      _result[_result.length] = matrix[Len - 1 - layer][index]
    }
    // ↑
    for (
      var index = Len - 1 - layer;
      index > layer && _result.length < maxLen;
      index--
    ) {
      _result[_result.length] = matrix[index][layer]
    }
    layer++
  }
  return _result
}

img

官方答案

  • 模拟 可以模拟打印矩阵的路径。初始位置是矩阵的左上角,初始方向是向右,当路径超出界限或者进入之前访问过的位置时,则顺时针旋转,进入下一个方向。

判断路径是否进入之前访问过的位置需要使用一个与输入矩阵大小相同的辅助矩阵 \textit{visited}visited,其中的每个元素表示该位置是否被访问过。当一个元素被访问时,将 \textit{visited}visited 中的对应位置的元素设为已访问。

如何判断路径是否结束?由于矩阵中的每个元素都被访问一次,因此路径的长度即为矩阵中的元素数量,当路径的长度达到矩阵中的元素数量时即为完整路径,将该路径返回。

class Solution {
    public int[] spiralOrder(int[][] matrix) {
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return new int[0];
        }
        int rows = matrix.length, columns = matrix[0].length;
        boolean[][] visited = new boolean[rows][columns];
        int total = rows * columns;
        int[] order = new int[total];
        int row = 0, column = 0;
        int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
        int directionIndex = 0;
        for (int i = 0; i < total; i++) {
            order[i] = matrix[row][column];
            visited[row][column] = true;
            int nextRow = row + directions[directionIndex][0], nextColumn = column + directions[directionIndex][1];
            if (nextRow < 0 || nextRow >= rows || nextColumn < 0 || nextColumn >= columns || visited[nextRow][nextColumn]) {
                directionIndex = (directionIndex + 1) % 4;
            }
            row += directions[directionIndex][0];
            column += directions[directionIndex][1];
        }
        return order;
    }
}
  • 按层模拟

可以将矩阵看成若干层,首先打印最外层的元素,其次打印次外层的元素,直到打印最内层的元素。

定义矩阵的第 kk 层是到最近边界距离为 kk 的所有顶点。例如,下图矩阵最外层元素都是第 11 层,次外层元素都是第 22 层,剩下的元素都是第 33 层。

[
  [1, 1, 1, 1, 1, 1, 1],
  [1, 2, 2, 2, 2, 2, 1],
  [1, 2, 3, 3, 3, 2, 1],
  [1, 2, 2, 2, 2, 2, 1],
  [1, 1, 1, 1, 1, 1, 1]
]

对于每层,从左上方开始以顺时针的顺序遍历所有元素。假设当前层的左上角位于(top,left),右下角位于(bottom,right),按照如下顺序遍历当前层的元素。

从左到右遍历上侧元素,依次为(top,left) 到(top,right)。

从上到下遍历右侧元素,依次为(top+1,right) 到(bottom,right)。

如果 left < right 且 top < bottom,则从右到左遍历下侧元素,依次为 :

  • (bottom,right−1)
  • 到 (bottom,left+1) 以及从下到上遍历左侧元素,依次为:
  • (bottom,left)
  • (top+1,left)。

遍历完当前层的元素之后,将 left 和 top 分别增加 1,将 right 和 bottom 分别减少 1,进入下一层继续遍历,直到遍历完所有元素为止。

img

class Solution {
    public int[] spiralOrder(int[][] matrix) {
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return new int[0];
        }
        int rows = matrix.length, columns = matrix[0].length;
        int[] order = new int[rows * columns];
        int index = 0;
        int left = 0, right = columns - 1, top = 0, bottom = rows - 1;
        while (left <= right && top <= bottom) {
            for (int column = left; column <= right; column++) {
                order[index++] = matrix[top][column];
            }
            for (int row = top + 1; row <= bottom; row++) {
                order[index++] = matrix[row][right];
            }
            if (left < right && top < bottom) {
                for (int column = right - 1; column > left; column--) {
                    order[index++] = matrix[bottom][column];
                }
                for (int row = bottom; row > top; row--) {
                    order[index++] = matrix[row][left];
                }
            }
            left++;
            right--;
            top++;
            bottom--;
        }
        return order;
    }
}

高手在民间

  • 思路基本和“按层模拟” 一致
/**
 * @param {number[][]} matrix
 * @return {number[]}
 */
var spiralOrder = function (matrix) {
  if (matrix.length === 0) return []
  if (matrix[0].length === 0) return []
  let res = []
  let startX = 0,
    startY = 0
  let endX = matrix.length - 1,
    endY = matrix[0].length - 1
  while (startX <= endX && startY <= endY) {
    for (let i = startY; i <= endY; i++) {
      res.push(matrix[startX][i])
    }
    for (let i = startX + 1; i <= endX; i++) {
      res.push(matrix[i][endY])
    }
    for (let i = endY - 1; i >= startY; i--) {
      if (startX === endX) return res
      res.push(matrix[endX][i])
    }
    for (let i = endX - 1; i > startX; i--) {
      if (startY === endY) return res
      res.push(matrix[i][startY])
    }
    startX++
    startY++
    endX--
    endY--
  }
  return res
}

菜鸡的自白

优化说明:

  • 我的解法终止条件不优雅,应该可以从循环中就可以得到终止的条件
  • 循环使用复杂判断来限制可以参考“按层模拟”来明确循环方向
  • 以下引用别人类似解法(感觉看起来更容易理解):
    • layer 用 i(行),j(列) 代替
/**
 * @param {number[][]} matrix
 * @return {number[]}
 */
var spiralOrder = function (matrix) {
  const Len = matrix.length
  if (!Len) {
    return []
  }

  const I = matrix[0].length
  if (!n) {
    return []
  }

  const _result = []
  let i = 0,
    j = 0
  // 这里的终止条件是: i <= (Len - 1) / 2 与 j <= (n - j) / 2
  // 即最里面的那层左上角元素的坐标
  while (i <= Len - i - 1 && j <= n - j - 1) {
    for (let col = j; col <= n - j - 1; ++col) {
      _result.push(matrix[i][col])
    }

    for (let row = i + 1; row <= Len - i - 1; ++row) {
      _result.push(matrix[row][n - j - 1])
    }

    if (i < Len - i - 1 && j < n - j - 1) {
      for (let col = n - j - 2; col > j; --col) {
        _result.push(matrix[m - i - 1][col])
      }

      for (let row = Len - i - 1; row > i; --row) {
        _result.push(matrix[row][j])
      }
    }

    i++
    j++
  }

  return results
}

博客: 前端小书童

每天的每日一题,写的题解会同步更新到公众号一天一大 lee 栏目 欢迎关注留言

公众号:前端小书童