LeetCode 54. 螺旋矩阵(中)& 剑指offer 面试题29. 顺时针打印矩阵(易)
最外层所有元素按照顺时针顺序输出,其次是次外层,以此类推。
我们定义矩阵的第 k 层是到最近边界距离为 k 的所有顶点。
题目
给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。
示例
示例 1: 1
2
3
4
5
6
7输入:
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
输出: [1,2,3,6,9,8,7,4,5]
1 |
|
测试用例
数组中有多行多列;数组中只有一行;数组中只有一列;数组中只有一行一列。
本题考点
本题主要考查应聘者的思维能力。从外到内顺时针打印矩阵这个过程非常复杂,应聘者如何能很快地找出其规律并写出完整的代码,是解这道题的关键。当问题比较抽象不容易理解时,可以试着画几幅图形帮助理解,这样往往能更快地找到思路。
考察知识点
数组
核心思想
最外层所有元素按照顺时针顺序输出,其次是次外层,以此类推。
我们定义矩阵的第 k 层是到最近边界距离为 k 的所有顶点。例如,下图矩阵最外层元素都是第 1 层,次外层元素都是第 2 层,然后是第 3 层的。
1 |
|
对于每层,我们从左上方开始以顺时针的顺序遍历所有元素,假设当前层左上角坐标是 \(\text{(r1, c1)}\),右下角坐标是 \(\text{(r2, c2)}\)。
首先,遍历上方的所有元素 \(\text{(r1, c)}\),按照 \(\text{c = c1,...,c2}\) 的顺序。然后遍历右侧的所有元素 \(\text{(r, c2)}\),按照 \(\text{r = r1+1,...,r2}\) 的顺序。如果这一层有四条边(也就是 \(\text{r1 < r2}\) 并且 \(\text{c1 < c2}\) ),我们以下图所示的方式遍历下方的元素和左侧的元素。

算法
- 特判matrix为空的情况,直接返回[]。
- 声明
r1
为行起点0
,r2
为行终点len(matrix)
。 - 声明
c1
为列起点0
,c2
为列终点len(matrix[0]) - 1
。 - 进入
while
循环,一次while
循环,就是一层(top、left、bottom、right),终止条件是r1 > r2 and c1 > c2
,如果满足终止条件,就说明最里面一层也循环。for
循环,调用迭代函数spiral_coords
处理r1, c1, r2, c2
。- 更新行,行起点(
r1
)增加,行终点(r2
)减小。 这样就把已经遍历了的right和left给去掉了。 - 更新列,列起点(
c1
)增加,列终点(c2
)减小。这样就把已经遍历了的top和bottom给去掉了。
- return ans
- 声明
spiral_coords(r1, c1, r2, c2)
函数,根据传入的行和列的起点和终点,分离相应的坐标。- 首先循环
top
,特点是行不变,列变化,c
从c1
变化到c2
,yield r1, c
。 - 然后循环
right
,特点是列不变,行变化,r
从r1
变化到r2
,yield r, c2
。 - 接着判断当前位置,如果满足条件,
r1 < r2 and c1 < c2
,则说明还没结束,还有bottom和left可以遍历。- 循环bottom,特点是行不变,列变化,
c
从c2+1
变化到c1+1
,yield r2, c
。 - 循环left,特点是列不变,行变化,
r
从r2+1
变化到r1+1
,yield r, c1
。
- 循环bottom,特点是行不变,列变化,
- 首先循环
Python版本
1 |
|
时间复杂度: \(O(N)\),其中 N 是输入矩阵所有元素的个数。因为我们将矩阵中的每个元素都添加进答案里。
空间复杂度: \(O(N)\),需要矩阵 ans 存储信息。
执行用时 :52 ms, 在所有 Python3 提交中击败了11.14%的用户。
内存消耗 :13.4 MB, 在所有 Python3 提交中击败了18.94%的用户。
C++版本
1 |
|
有效语法糖
1、python中 yield
的用法
- 什么是yield?一个将普通函数变成迭代器的关键字。
首先,可以将 yield
视作 return
,是在程序中返回某个值,返回之后程序就不再往下运行了。 其次,做一个是生成器(generator)的一部分(带yield的函数才是真正的迭代器)。
1 |
|
注意,第二次 print(next(g))
是在第一次 print(next(g))
执行之后再执行的,不过不同的是,这个时候是从刚才那个next程序停止的地方开始执行的,也就是要执行res的赋值操作。
yield
的含义:带yield的函数是一个生成器,而不是一个函数了,这个生成器有一个函数就是 next
函数,next
就相当于“下一步”生成哪个数,这一次的 next
开始的地方是接着上一次的 next
停止的地方执行的,所以调用 next
的时候,生成器并不会从 foo
函数的开始执行,只是接着上一步停止的地方开始,然后遇到 yield
后,return
出要生成的数,此步就结束。
那么,如何向带有 yield
的函数传递参数?
1 |
|
再看一个这个生成器的send函数的例子,这个例子就把上面那个例子的最后一行换掉了,输出结果:
1 |
|
send
是发送一个参数给 res
的,因为上面讲到,return的时候,并没有把4赋值给 res
,下次执行的时候只好继续执行赋值操作,只好赋值为None了,而如果用 send
的话,开始执行的时候,先接着上一次(return 4之后)执行,先把7赋值给了 res
,然后执行 next
的作用,遇见下一回的 yield
,return出结果后结束。
为什么要用 yield
?可以灵活的将函数改成迭代器。 比如说取 0,1,2,3,4,5,6,7,8,9,10
1). 使用range迭代器 1
2
3print("staring...")
for i in range(10):
print(i)
1 |
|
2). 使用 yield
设计迭代器 1
2
3
4
5
6
7
8def foo(num, target):
print("staring...")
while num < target-1:
num += 1
yield num
for n in foo(-1, 10):
print(n)
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!