我参加了我们想学院的一个工作室,下面是第二次面试的试题,给了我们两天时间去准备:
我们手中有一个大小为N的整数集合I。其中的整数为1,2,…,N,在这N个数的排列中。有的排列满足性质:该排列中除了最后一个整数外的每个整数后面都跟有(不必直接紧跟)一个同它相差为1的整数。例如:N=5.排列51432是满足该性质的,而52431则不满足。
1)任给一个N,能够得出对应于N的满足上述性质的排列个数吗?
2)设有一个N个数的排列,已知其中P(P<=N)个位置上的数。求共有多少个满足上述性质的排列.例如:N=5,且已知第一位,第二位,第三位分别是5,1和4。则51432,51423就是满足上述性质的两个排列.
对于这种题目,穷举法是绝对不行的了,因为问题规模的增长是N!,要多可怕就有多可怕。
我最初的想法是用图论的方法来做,考虑N个标上编码的点,要求找出所有符合如下要求的单向路径的数量:除了最后一个点之外,每一个点都与编号和该点编号相差1的点单连通。
我认为这个思路是可行的,但是我没有根据这种思路做出来。姑且留待以后进行研究。
然后我采用最基本的方法来做,从1点的情况开始,列举所有的情况,观察其规律。
结果第一次做的时候N=4的情况我数错了,数成了9,而且N=5的情况是16,我以为对N>=3,符合要求的路径会是N^2,之后用数学归纳法进行证明,死都证明不出来。
当问题遇到瓶颈的时候,按照做数模的习惯,我会先去搜索资料,结果发现这道题是湖南某大学的ACM集训题。
在这种情况下面,答案被搜出来了,不过我草草看了一眼,没有看懂,还是依照自己的想法去做。不过既然知道了是ACM题,所以就请教了一下我们班的ACM高手。他简单地分析了一下,说第一问的答案是2^(N-1),第二问的分析我没有看。
然后我重新整理一下思路,第一问的答案果然是2^(N-1)。证明如下:
称如下序列满足性质A:长为N的序列由1~N个数字组成,每个数字不重不漏,除了最后一个数字之外,每个数字后面一定要有一个与它本身相差1的数字。
引理:一个满足性质A的序列,其第一位必定是1或者N
完整的证明我不写了(因为懒)。
命题1:对于N,长度为N时,满足性质A的序列的数目是2^(N-1)。
这个命题用归纳法可以证明,N=1时当然成立,当N=n的时候,按照归纳假设有2^(n-1)个序列,然后对第一位是N+1和1的情况进行考虑,当第一位是1的时候,可以如下处理,通过双射,f:x->x-1,把2~n+1变成1~n来处理。
第一问就解决了。
到了第二问,我还是百思不得其解。最后,我还是根据我搜索到的集训题的答案来写程序。说到底,也只是理解别人的算法而已。
其中有一个命题是非常重要的:满足A性质的序列在去掉了第一个数字之后的子序列还是满足A性质的。
所以,采用递归解决。算法就不写在这里了,只说一下基本的输入输出:
输入一个列向量A,表示初始状态,如果某个位置上的数字没有确定,则用0表示;
整数s、r:我们要寻找s到s+r-1的r个数中所有符合性质A的序列的数量;
整数g:符合性质A的序列的数量。
下面是Matlab的代码:
1 function g = NewArr(A,s,r)
2
3 %%
4 %A:表示初始状态的向量,如果某个位置不确定,用0表示;
5 %s:我们要寻找从s..s+r-1 中r个数符合题设条件的序列数量;
6 %r:就是上面的r;
7 %注意:Length(A)==r 必须成立;
8 %g:符合条件的序列的数量
9 %%
10
11 Init = A;
12 Min = s;
13 Num = r;
14
15 Temp = A(2:Num);
16
17 if r == 1
18 g = 1;
19 return
20 end
21
22 if Init(1) == Min
23 g = NewArr(Temp,Min+1,Num-1);
24 return
25 end
26
27 if Init(1) == (Min + Num -1)
28 g = NewArr(Temp,Min,Num-1);
29 return;
30 end
31
32 if Init(1) == 0
33 g = NewArr(Temp,Min,Num-1) + NewArr(Temp,Min+1,Num-1);
34 return
35 end