【算法-LeetCode】54. 螺旋矩阵(二维数组)

54. 螺旋矩阵 - 力扣(LeetCode)

发布:2021年8月18日17:09:11

问题描述及示例

给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

示例 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]

提示:
m == matrix.length
n == matrix[i].length
1 <= m, n <= 10
-100 <= matrix[i][j] <= 100

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/spiral-matrix
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

我的题解

我觉得这道题的难点就在于对二维数组下标的控制,因为在螺旋遍历过程中,下标不是简单地在一个维度单调变化,常规的对二维数组的遍历需要对内层循环中对第二维下标在开始时置零,但是螺旋遍历的逻辑就更复杂了。

可以通过下面的图③发现,完全可以把螺旋遍历当成是由外层向内层的逐层遍历。
在这里插入图片描述
于是我就开始将螺旋遍历的问题简化为了单层遍历的问题,自然而然地就想到了将遍历一圈的逻辑封装成一个函数,只要传入一个数组以及相应的遍历范围就可以返回某一层的元素。于是就有了下面程序中的traverseWrapper()函数,wrapper是包装的意思。这个函数就用于获取二维数组中某一圈的元素。只要将matrix数组中的每一圈的遍历结果按顺序拼接在一起就是我们想要的最终结果。

traverseWrapper()函数中的四个for循环从上到下分别用于遍历某一圈中的上、右、下、左四个边。函数中的wrapper数组用于存储当前圈的遍历结果。

同时在遍历过程中要注意出入的 range参数的变化规律,可以观察上图中的表格。可得range =[i, col - i, i, row - i]

注意:由于题目要求是按顺时针遍历数组,所以下面四个for循环的顺序不能打乱。

解决了遍历某一圈的难题,就只剩下要调用几次traverseWrapper()函数的问题了。matrix有几圈,就要调用几次traverseWrapper()。那么如何确定二维数组有多少圈呢?观察上面的图,可发现,其实只要把二维数组裁剪成一个最大的正方形,这个正方形的边长除以二,再对结果进行向上取整操作所得的即为我们想要的圈数。把二维数组裁剪成一个最大的正方形并取边长这个操作其实就等效于取二维数组的行数和列数中的较小值。所以圈数为:wrapperNum = Math.ceil(Math.min(row + 1, col + 1) / 2)。详解请看下方注释。

/**
 * @param {number[][]} matrix
 * @return {number[]}
 */
var spiralOrder = function (matrix) {
  // row和col用于获取矩阵的长和宽信息,注意这里的row和col是相应的下标范围
  let row = matrix.length - 1;
  let col = matrix[0].length - 1;
  // result用于存储最终结果
  let result = [];
  // 计算矩阵有几圈,以便在下方的for循环中控制traverseWrapper的调用次数
  let wrapperNum = Math.ceil(Math.min(row + 1, col + 1) / 2);
  for (let i = 0; i < wrapperNum; i++) {
    // wrapper用于接收当前层的遍历结果
    let wrapper = traverseWrapper(matrix, [i, col - i, i, row - i]);
    // 用数组对象的concat函数拼接各圈的遍历结果
    result = result.concat(wrapper);
  }
  return result;
};

// range = [colStart, colEnd, rowStart, rowEnd]
// traverseWrapper用于获取一个二维数组中的某一圈,range参数用于控制所遍历的圈
function traverseWrapper(arr, range) {
  // wrapper用于存储当前遍历的圈中的元素
  let wrapper = [];
  // 用于遍历当前圈中的上边
  for (let i = range[0]; i <= range[1] - 1; i++) {
    wrapper.push(arr[range[2]][i]);
  }
  // 用于遍历当前圈中的右边
  for (let i = range[2]; i <= range[3]; i++) {
    wrapper.push(arr[i][range[1]]);
  }
  // 用于遍历当前圈中的下边,注意下面两个for循环中都有一个if判断,
  // 这是为了防止当前圈只有一行或一列的情况,详见下方【补充】
  for (let i = range[1] - 1; i >= range[0] + 1; i--) {
    if(range[2] === range[3]) {
      break;
    }
    wrapper.push(arr[range[3]][i]);
  }
  // 用于遍历当前圈中的左边
  for (let i = range[3]; i >= range[2] + 1; i--) {
    if(range[0] === range[1]) {
      break;
    }
    wrapper.push(arr[i][range[0]]);
  }
  return wrapper;
}


提交记录
39 / 39 个通过测试用例
状态:通过
执行用时:68 ms, 在所有 JavaScript 提交中击败了82.40%的用户
内存消耗:37.4 MB, 在所有 JavaScript 提交中击败了90.71%的用户
时间:2021/08/18 17:14

补充
注意到上面的traverseWrapper()函数的后两个for循环(下边和左边的遍历)中都有一个if判断,这是为了防止当前圈只有一行或一列的情况。

console.log(spiralOrder([[3], [2], [1], [0]])); // 只有一列元素
console.log(spiralOrder([[4,5,6,7]])); // 只有一行元素

// 控制台输出
(7) [3, 2, 1, 0, 0, 1, 2]
(6) [4, 5, 6, 7, 6, 5]

可以看到,在当前圈只有一行或一列时,可能会使得某些元素被重复计算,关键就在于第三个for循环中i的条件控制没有判断当前遍历是对【下边】的遍历还是对【上边】的遍历;第四个for循环中i的条件控制没有判断当前遍历是对【右边】的遍历还是对【左边】的遍历。所以导致在当前圈只有一列或一行时会重复访问某些元素。所以应该给后两个for循环加上相应的if判断。

可以在Chrome的开发者工具中进行逐步调试以观察上述过程,这样可以对为什么要给后两个for循环加if判断有更深的体会。

官方题解

更新:2021年7月29日18:43:21

因为我考虑到著作权归属问题,所以【官方题解】部分我不再粘贴具体的代码了,可到下方的链接中查看。

更新:2021年8月18日17:19:39

参考:螺旋矩阵 - 螺旋矩阵 - 力扣(LeetCode)

【更新结束】

有关参考

更新:2021年8月18日17:13:24
参考:JavaScript concat() 方法

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值