八皇后问题的具体背景就不重复叙述了,下面直接给出解法。
该解法与巨著《structure and interpretation of computer program》中用Scheme代码提供的解法几乎是相同,具备函数式编程的风格。
“函数式编程的思路是自上而下的。它首先提出一个大问题,在最高层用一个函数来解决这个大问题。在这个函数内部,再用其他函数来解决小问题。再这样递归式的分解下,直到问题得到解决。”–Vamei《从Python开始学编程》
递归是解决此类问题的利器。递归的思想是找到
f
(
n
)
f(n)
f(n)和
f
(
n
+
1
)
f(n+1)
f(n+1)之间的递归关系,以及边界值。
具体实践中,我自己总结的方法论是“小而大之”和“大而小之”两个步骤。
所谓小而大之,就是将具体问题扩展成一般问题,而待求解的问题只是更一般问题的一个具体情况。
大而小之,就是要对这个一般问题,考虑它非常小的情形。从小的情况得到边界值以及递归关系的信息。对解决问题是非常有帮助的。
首先,对于 n n n皇后问题,有 n n n个皇后,且棋盘的大小也是 n n n.
当规模确定后,可以把 n n n皇后的解法问题看成是逐步生成的。首先放置第一列,解为 f ( 1 ) f(1) f(1),然后放置第二列,解为 f ( 2 ) f(2) f(2),直至求出 f ( n ) f(n) f(n)。 f ( n ) f(n) f(n)返回的是列表的列表,每个子列表代表一种解法。
对于
f
(
n
)
f(n)
f(n),当
n
=
1
n=1
n=1时,代表放置第一列,那么将有
n
n
n种解法,结果为
f
(
1
)
f(1)
f(1):
[[1], [2], [3], …, [n]]
当
n
>
1
n>1
n>1时,对
f
(
n
−
1
)
f(n-1)
f(n−1)中的每一种解法添加第
n
n
n列的放法,首先将所有可能的放法都添加进去,将得到
n
f
(
n
−
1
)
nf(n-1)
nf(n−1)种放法,然后过滤掉其中不满足要求的放法,就可以得到
f
(
n
)
f(n)
f(n)。
Big question 的解法如下:
def solution(n):
scale = n
def f(k):
if k == 1:
return [[x] for x in range(1, scale + 1)]
else:
last = f(k-1)
return list(filter(safe, [x+[y] for x in last for y in range(1, scale+1)]))
return f(n)
其中safe函数用来判断当前的放法是否满足要求,联想函数式编程的思想,safe只要具备判断一解法是否合法即可,与其具体实现是无关的。
在由 f ( n ) f(n) f(n)求 f ( n + 1 ) f(n+1) f(n+1)时,要判断新添加的列是否满足要求,只需满足两个条件:
- 不与之前的任一个皇后同行
- 不与之前的任一个皇后在同一斜线上
下面是safe函数的定义:
def safe(x):
for i in range(len(x)-1):
if (x[-1] == x[i]) or (abs(x[-1] - x[i]) == (len(x)-1) - i):
return False
return True
然后我们把safe函数的定义放到solution函数的内部即可,以形成一种所谓的块结构:
def solution(n):
scale = n
def safe(x):
for i in range(len(x)-1):
if (x[-1] == x[i]) or (abs(x[-1] - x[i]) == (len(x)-1) - i):
return False
return True
def f(k):
if k == 1:
return [[x] for x in range(1, scale + 1)]
else:
last = f(k-1)
return list(filter(safe, [x+[y] for x in last for y in range(1, scale+1)]))
return f(n)
运行如下:
>>>len(solution(8))
92
>>>[len(solution(x)) for x in range(1, 10)]
[1, 0, 0, 2, 10, 4, 40, 92, 352]