如下图,有一段人字形的铁路,火车只能从右边铁路线驶进,并且只能从左边铁路线驶出。驶进来的火车可以停在人字形的上半部分铁路线上,从而让后进来的火车先从左边铁路线驶出。当然它也可以进来之后不作停留直接驶出。假设右边有n列火车,问从左边驶出的n列火车的排列有多少种?
比如假设右边有3列火车A、B、C,则从左边驶出的火车的排列只有5种:ABC、ACB、BAC、BCA和CBA。3列火车的所有6种排列里唯有CAB是不可能的。因为C要想第一个出来,则A和B必须进入人字形铁路的上半部分不出来,等C进入再出来之后再出来。此时,列车出来的顺序只能是CBA,而不可能是CAB。
显然,这一题的输入参数是右边等待进入铁路的火车的数量n。这当然没有错误,可问题是接下来的递归推导比较困难。为了解决这个难题,我们再增加一个参数m,表示待在铁路上半部分的火车的数量,初值是0。
显然,递归边界是n = 0。此时,不论m等于多少,这些火车都只能按照次序一一驶出。所以排列数是1。
递归假设比较好理解,我们这里略过不提。对于递归推导,当两个参数分别是n、m时,我们有两种方法分解问题。第一,右边等待的n列火车中的第一列开进铁路,这时问题参数分别转化为n-1、m+1;第二,停在上半部分铁路上的m列火车中的第一列驶出,这时问题参数分别转化为n、m-1,当然前提是m>0。这两种方法得到的排列不会重复,并且不可能存在某个排列是这两种方法覆盖不了的。所以我们分别用这两组参数进行递归调用,再把结果相加即可。代码如下:
def get_trains(n, m=0):
if n == 0:
return 1
result = get_trains(n-1, m+1)
if m > 0:
result += get_trains(n, m-1)
return result
if __name__ == '__main__':
for n in range(1, 10+1):
print(n, get_trains(n))
运行结果如下:
1 1
2 2
3 5
4 14
5 42
6 132
7 429
8 1430
9 4862
10 16796
这个问题有意思的地方在于,虽然m的值加1了,但是子问题的参数还是比原问题更靠近递归边界,这是因为递归边界仅跟参数n有关,跟m无关。