发布: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() 方法