参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们受益!
54.螺旋矩阵
给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
示例1:
输入: [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ] ] 输出:[1,2,3,6,9,8,7,4,5]
思路
本题解决思路继承自59.螺旋矩阵II,建议看完59.螺旋矩阵II之后再看本题
与59.螺旋矩阵II相同的是:两者都是模拟矩形的顺时针旋转,所以核心依然是依然是坚持循环不变量,按照左闭右开的原则
模拟顺时针画矩阵的过程:
- 填充上行从左到右
- 填充右列从上到下
- 填充下行从右到左
- 填充左列从下到上
由外向内一圈一圈这么画下去,如下所示:
这里每一种颜色,代表一条边,我们遍历的长度,可以看出每一个拐角处的处理规则,拐角处让给新的一条边来继续画。
与59.螺旋矩阵II不同的是:前题中的螺旋矩阵是正方形,只有正方形的边长n一个边界条件,而本题中,需要考虑长方形的长和宽(m行和n列)两个边界条件。自然,m可以等于n,即前题可视为本题在m==n的特殊情况。
我们从最一般的情况开始考虑,与59.螺旋矩阵II题解对比起来,m和n的带入,主要引来两方面的差异:
- loop的计算: 本题的loop计算与59.螺旋矩阵II算法略微差异,因为存在rows和columns两个维度,可自行分析,loop只能取min(rows, columns),例如rows = 5, columns = 7,那loop = 5 / 7 = 2
- mid的计算及填充: 1、同样的原理,本题的mid计算也存在上述差异; 2、 如果min(rows, columns)为偶数,则不需要在最后单独考虑矩阵最中间位置的赋值 如果min(rows, columns)为奇数,则矩阵最中间位置不只是[mid][mid],而是会留下来一个特殊的中间行或者中间列,具体是中间行还是中间列,要看rows和columns的大小,如果rows > columns,则是中间列,相反,则是中间行
代码如下,已经详细注释了每一步的目的,可以看出while循环里判断的情况是很多的,代码里处理的原则也是统一的左闭右开。
整体C++代码如下:
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
if (matrix.size() == 0 || matrix[0].size() == 0)
return {};
int rows = matrix.size(), columns = matrix[0].size();
int total = rows * columns;
vector<int> res(total); // 使用vector定义一个一维数组存放结果
int startx = 0, starty = 0; // 定义每循环一个圈的起始位置
int loop = min(rows, columns) / 2;
// 本题的loop计算与59.螺旋矩阵II算法略微差异,因为存在rows和columns两个维度,可自行分析,loop只能取min(rows, columns),例如rows = 5, columns = 7,那loop = 5 / 7 = 2
int mid = min(rows, columns) / 2;
// 1、同样的原理,本题的mid计算也存在上述差异;
// 2、
//如果min(rows, columns)为偶数,则不需要在最后单独考虑矩阵最中间位置的赋值
//如果min(rows, columns)为奇数,则矩阵最中间位置不只是[mid][mid],而是会留下来一个特殊的中间行或者中间列,具体是中间行还是中间列,要看rows和columns的大小,如果rows > columns,则是中间列,相反,则是中间行
//相信这一点不好理解,建议自行画图理解
int count = 0;// 用来给矩阵中每一个空格赋值
int offset = 1;// 每一圈循环,需要控制每一条边遍历的长度
int i,j;
while (loop --) {
i = startx;
j = starty;
// 下面开始的四个for就是模拟转了一圈
// 模拟填充上行从左到右(左闭右开)
for (j = starty; j < starty + columns - offset; j++) {
res[count++] = matrix[startx][j];
}
// 模拟填充右列从上到下(左闭右开)
for (i = startx; i < startx + rows - offset; i++) {
res[count++] = matrix[i][j];
}
// 模拟填充下行从右到左(左闭右开)
for (; j > starty; j--) {
res[count++] = matrix[i][j];
}
// 模拟填充左列从下到上(左闭右开)
for (; i > startx; i--) {
res[count++] = matrix[i][starty];
}
// 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
startx++;
starty++;
// offset 控制每一圈里每一条边遍历的长度
offset += 2;
}
// 如果min(rows, columns)为奇数的话,需要单独给矩阵最中间的位置赋值
if (min(rows, columns) % 2) {
if(rows > columns){
for (int i = mid; i < mid + rows - columns + 1; ++i) {
res[count++] = matrix[i][mid];
}
} else {
for (int i = mid; i < mid + columns - rows + 1; ++i) {
res[count++] = matrix[mid][i];
}
}
}
return res;
}
};
类似题目
其他语言版本
Java
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
//存放数组的数
List<Integer> ans = new ArrayList<>();
//列数
int columns = matrix[0].length;
//行数
int rows = matrix.length;
//遍历起点
int start = 0;
//循环的次数 行数和列数中的最小值除以二
int loop = Math.min(rows,columns) / 2;
//未遍历的中间列(行)的列(行)下标
int mid = loop;
//终止条件
int offSet = 1;
int i,j;
while(loop-- > 0) {
//初始化起点
i = j = start;
//从左往右
for(; j < columns - offSet; j++)
ans.add(matrix[i][j]);
//从上往下
for(; i < rows - offSet; i++)
ans.add(matrix[i][j]);
//从右往左
for(; j > start; j--)
ans.add(matrix[i][j]);
//从下往上
for(; i > start; i--)
ans.add(matrix[i][j]);
//每循环一次 改变起点位置
start++;
//终止条件改变
offSet++;
}
//如果行和列中的最小值是奇数 则会产生中间行或者中间列没有遍历
if(Math.min(rows,columns) % 2 != 0) {
//行大于列则产生中间列
if(rows > columns) {
//中间列的行的最大下标的下一位的下标为mid + rows - columns + 1
for(int k = mid; k < mid + rows - columns + 1; k++) {
ans.add(matrix[k][mid]);
}
}else {//列大于等于行则产生中间行
//中间行的列的最大下标的下一位的下标为mid + columns - rows + 1
for(int k = mid; k < mid + columns - rows + 1; k++) {
ans.add(matrix[mid][k]);
}
}
}
return ans;
}
}
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> res = new ArrayList<>(); // 存放结果
if (matrix.length == 0 || matrix[0].length == 0)
return res;
int rows = matrix.length, columns = matrix[0].length;
int startx = 0, starty = 0; // 定义每循环一个圈的起始位置
int loop = 0; // 循环次数
int offset = 1; // 每一圈循环,需要控制每一条边遍历的长度
while (loop < Math.min(rows, columns) / 2) {
int i = startx;
int j = starty;
// 模拟填充上行从左到右(左闭右开)
for (; j < columns - offset; j++) {
res.add(matrix[i][j]);
}
// 模拟填充右列从上到下(左闭右开)
for (; i < rows - offset; i++) {
res.add(matrix[i][j]);
}
// 模拟填充下行从右到左(左闭右开)
for (; j > starty; j--) {
res.add(matrix[i][j]);
}
// 模拟填充左列从下到上(左闭右开)
for (; i > startx; i--) {
res.add(matrix[i][j]);
}
// 起始位置加1 循环次数加1 并控制每条边遍历的长度
startx++;
starty++;
offset++;
loop++;
}
// 如果列或行中的最小值为奇数 则一定有未遍历的部分
// 可以自行画图理解
if (Math.min(rows, columns) % 2 == 1) {
// 当行大于列时 未遍历的部分是列
// (startx, starty)即下一个要遍历位置 从该位置出发 遍历完未遍历的列
// 遍历次数为rows - columns + 1
if (rows > columns) {
for (int i = 0; i < rows - columns + 1; i++) {
res.add(matrix[startx++][starty]);
}
} else {
// 此处与上面同理 遍历完未遍历的行
for (int i = 0; i < columns - rows + 1; i++) {
res.add(matrix[startx][starty++]);
}
}
}
return res;
}
}
JavaScript
/**
* @param {number[][]} matrix
* @return {number[]}
*/
var spiralOrder = function(matrix) {
let m = matrix.length
let n = matrix[0].length
let startX = startY = 0
let i = 0
let arr = new Array(m*n).fill(0)
let offset = 1
let loop = mid = Math.floor(Math.min(m,n) / 2)
while (loop--) {
let row = startX
let col = startY
// -->
for (; col < n + startY - offset; col++) {
arr[i++] = matrix[row][col]
}
// down
for (; row < m + startX - offset; row++) {
arr[i++] = matrix[row][col]
}
// <--
for (; col > startY; col--) {
arr[i++] = matrix[row][col]
}
for (; row > startX; row--) {
arr[i++] = matrix[row][col]
}
startX++
startY++
offset += 2
}
if (Math.min(m, n) % 2 === 1) {
if (n > m) {
for (let j = mid; j < mid + n - m + 1; j++) {
arr[i++] = matrix[mid][j]
}
} else {
for (let j = mid; j < mid + m - n + 1; j++) {
arr[i++] = matrix[j][mid]
}
}
}
return arr
};
Python
class Solution(object):
def spiralOrder(self, matrix):
"""
:type matrix: List[List[int]]
:rtype: List[int]
"""
if len(matrix) == 0 or len(matrix[0]) == 0 : # 判定List是否为空
return []
row, col = len(matrix), len(matrix[0]) # 行数,列数
loop = min(row, col) // 2 # 循环轮数
stx, sty = 0, 0 # 起始x,y坐标
i, j =0, 0
count = 0 # 计数
offset = 1 # 每轮减少的格子数
result = [0] * (row * col)
while loop>0 :# 左闭右开
i, j = stx, sty
while j < col - offset : # 从左到右
result[count] = matrix[i][j]
count += 1
j += 1
while i < row - offset : # 从上到下
result[count] = matrix[i][j]
count += 1
i += 1
while j>sty : # 从右到左
result[count] = matrix[i][j]
count += 1
j -= 1
while i>stx : # 从下到上
result[count] = matrix[i][j]
count += 1
i -= 1
stx += 1
sty += 1
offset += 1
loop -= 1
if min(row, col) % 2 == 1 : # 判定是否需要填充多出来的一行
i = stx
if row < col :
while i < stx + col - row + 1 :
result[count] = matrix[stx][i]
count += 1
i += 1
else :
while i < stx + row - col + 1 :
result[count] = matrix[i][stx]
count += 1
i += 1
return result
版本二:定义四个边界
class Solution(object):
def spiralOrder(self, matrix):
"""
:type matrix: List[List[int]]
:rtype: List[int]
"""
if not matrix:
return []
rows = len(matrix)
cols = len(matrix[0])
top, bottom, left, right = 0, rows - 1, 0, cols - 1
print_list = []
while top <= bottom and left <= right:
# 从左到右
for i in range(left, right + 1):
print_list.append(matrix[top][i])
top += 1
# 从上到下
for i in range(top, bottom + 1):
print_list.append(matrix[i][right])
right -= 1
# 从右到左
if top <= bottom:
for i in range(right, left - 1, -1):
print_list.append(matrix[bottom][i])
bottom -= 1
# 从下到上
if left <= right:
for i in range(bottom, top - 1, -1):
print_list.append(matrix[i][left])
left += 1
return print_list
Go
func spiralOrder(matrix [][]int) []int {
rows := len(matrix)
if rows == 0 {
return []int{}
}
columns := len(matrix[0])
if columns == 0 {
return []int{}
}
res := make([]int, rows * columns)
startx, starty := 0, 0 // 定义每循环一个圈的起始位置
loop := min(rows, columns) / 2
mid := min(rows, columns) / 2
count := 0 // 用来给矩阵中每一个空格赋值
offset := 1 // 每一圈循环,需要控制每一条边遍历的长度
for loop > 0 {
i, j := startx, starty
// 模拟填充上行从左到右(左闭右开)
for ; j < starty + columns - offset; j++ {
res[count] = matrix[startx][j]
count++
}
// 模拟填充右列从上到下(左闭右开)
for ; i < startx + rows - offset; i++ {
res[count] = matrix[i][j]
count++
}
// 模拟填充下行从右到左(左闭右开)
for ; j > starty; j-- {
res[count] = matrix[i][j]
count++
}
// 模拟填充左列从下到上(左闭右开)
for ; i > startx; i-- {
res[count] = matrix[i][starty]
count++
}
// 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
startx++
starty++
// offset 控制每一圈里每一条边遍历的长度
offset += 2
loop--
}
// 如果min(rows, columns)为奇数的话,需要单独给矩阵最中间的位置赋值
if min(rows, columns) % 2 == 1 {
if rows > columns {
for i := mid; i < mid + rows - columns + 1; i++ {
res[count] = matrix[i][mid]
count++
}
} else {
for i := mid; i < mid + columns - rows + 1; i++ {
res[count] = matrix[mid][i]
count++
}
}
}
return res
}
func min(x, y int) int {
if x < y {
return x
}
return y
}