蛇形数组算法是算法比赛和很多跟算法有关的地方最喜欢用的题目之一,于是在非常多的热爱算法的人手下也写出了很多有意思的蛇形数组算法,这里介绍一种几年前第一次见到蛇形数组在 codewars 上面的时候看到的一个非常精妙的算法。
蛇形数组
输入一个整数 n ,输出一个 n*n 的矩阵,要求矩阵的内容由外到内顺时针螺旋状填入依次递增的 1~n*n 的整数。
输入 : 5
输出 :
[[1, 2, 3, 4, 5],
[16, 17, 18, 19, 6],
[15, 24, 25, 20, 7],
[14, 23, 22, 21, 8],
[13, 12, 11, 10, 9]]
题解代码分析
n = input()
if not isinstance(n, int):print([])
d = iter(range(n ** 2))
a = r = [[[x, y] for y in range(n)] for x in range(n)]
while a:
for x, y in a[0]: r[x][y] = next(d) + 1
a = list(zip(*a[1:]))[::-1]
第一行:
n = int(input())
通过 input 和 int 函数获取输入的整数并存入变量 n 内
第二行:
if not isinstance(n, int):print([])
如果输入的内容不是整数,则直接返回一个空数组
第三行:
d = iter(range(n ** 2))
生成一个用于填入数字到矩阵内的数字迭代器对象
第四行:
a = r = [[[x, y] for y in range(n)] for x in range(n)]
创建两个相同的矩阵,大小均为 n*n。
a 矩阵是坐标矩阵,为了根据坐标在 r 矩阵中找到对应的位置并填入数字,所以说 a 矩阵是填数字的依据,而 r 举证是填入数字后的结果矩阵,也是最后输出的矩阵
矩阵生成方式是使用了正则表达式,生成了一个 n*n 的 “二维矩阵” ,里面每个元素分别是一个一维坐标向量 [x, y],其中 x 和 y 是索引。
n = 5 时,生成的矩阵为:
[[[0, 0], [0, 1], [0, 2], [0, 3], [0, 4]],
[[1, 0], [1, 1], [1, 2], [1, 3], [1, 4]],
[[2, 0], [2, 1], [2, 2], [2, 3], [2, 4]],
[[3, 0], [3, 1], [3, 2], [3, 3], [3, 4]],
[[4, 0], [4, 1], [4, 2], [4, 3], [4, 4]]]
第五行到第七行:
while a:
for x, y in a[0]: r[x][y] = next(d) + 1
a = list(zip(*a[1:]))[::-1]
算法部分的代码,循环条件当 a 矩阵不为空的时候进入循环。
第六行使用 for 循环,遍历坐标矩阵 a 的第一行,并使用 next 方法填入迭代器内的数字。
第七行:这是算法最精彩的地方
第七行对坐标矩阵 a 进行了更新。由内向外的看这行代码。
zip(*a[1:])
zip 函数是将两个序列对应内容两两组合后生成一个新的序列。这里通过 a[1:] 切片,将已经用完了的坐标(填完了数字的坐标)的一行除外,然后将剩下的部分结合在一起。a 前面加 * 表示,将 a 作为参数序列传入函数内,也就是将序列 a 里面的每个子序列分别作为 zip 的参数,传入 zip,而不是只将 a 作为一个序列传入zip。
具体看下面的演示
a 矩阵切片之前:
[[[0, 0], [0, 1], [0, 2], [0, 3], [0, 4]],
[[1, 0], [1, 1], [1, 2], [1, 3], [1, 4]],
[[2, 0], [2, 1], [2, 2], [2, 3], [2, 4]],
[[3, 0], [3, 1], [3, 2], [3, 3], [3, 4]],
[[4, 0], [4, 1], [4, 2], [4, 3], [4, 4]]]
a 矩阵切片之后:
[[[1, 0], [1, 1], [1, 2], [1, 3], [1, 4]],
[[2, 0], [2, 1], [2, 2], [2, 3], [2, 4]],
[[3, 0], [3, 1], [3, 2], [3, 3], [3, 4]],
[[4, 0], [4, 1], [4, 2], [4, 3], [4, 4]]]
将 a 矩阵通过 zip 组合,并使用 list 转化为列表对象之后:
[([1, 0], [2, 0], [3, 0], [4, 0]),
([1, 1], [2, 1], [3, 1], [4, 1]),
([1, 2], [2, 2], [3, 2], [4, 2]),
([1, 3], [2, 3], [3, 3], [4, 3]),
([1, 4], [2, 4], [3, 4], [4, 4])]
通过对照上面 zip 前后的数组可以发现,zip 将切片后的 a 矩阵的每一行的第 i 个元素组合在了一起,也就是说 zip 将每一列单独提取出来,作为一个元组,然后通过 list,将每一列的元组放在一个列表中。通过 zip 函数做到了将矩阵进行一个旋转。
但是可以看到,这个旋转是相当于逆时针旋转了一下,如果要顺时针需要对 zip 后的这个列表对象进行一个反序,所以第七行最后面使用切片 [::-1] 的方法进行了反序,最后反序后的上面 a 数组结果如下:
[([1, 4], [2, 4], [3, 4], [4, 4]),
([1, 3], [2, 3], [3, 3], [4, 3]),
([1, 2], [2, 2], [3, 2], [4, 2]),
([1, 1], [2, 1], [3, 1], [4, 1]),
([1, 0], [2, 0], [3, 0], [4, 0])]
完美的做到了顺时针旋转,通过 zip 和 反序的方法。
这样每次填完数字之后都对填过的坐标进行切片,然后对没填过的进行旋转,继续下一次循环填值,直到最后坐标数组为空,也就是没有没填过的坐标,之后输出结果矩阵 r
看完上面这些,知道了算法的本质之后,对于非正矩形的蛇形矩阵也能很容易进行修改
m, n = map(int, input().split())
if not isinstance(n, int) or not isinstance(m, int):print([])
d = iter(range(m*n))
a = r = [[[x, y] for y in range(m)] for x in range(n)]
while a:
for x, y in a[0]: r[x][y] = next(d) + 1
a = list(zip(*a[1:]))[::-1]
for i in r: print(i)
在输入的部分一次性获取两个值,然后生成数字的迭代器部分生成 1~m*n ,在矩阵创建里面也创建 m*n 的矩阵就可以很简单的实现了