问题描述
编号为1-N的N个士兵围坐在一起。从编号为1的士兵开始报数(1,2,3,等这样报)。数到m的士兵被枪毙出列。剩下的士兵再从1开始报。以此循环直到只剩1个士兵为止。求这个士兵的编号。
分析
用链表解决的方法在此不加赘述。重点叙述递归的方法。
假设在某次枪毙过后一共还有n个士兵:
每个黑点的数字代表在枪毙士兵之后士兵的编号。红色的叉所处的位置为刚刚被枪毙的士兵之前所处的位置。
很容易发现,当只剩1名士兵时,代表该名士兵的黑点上的编号为1。那么如果能够建立枪毙士兵的之前和枪毙士兵之后的所有士兵编号之间的关系,就可以很容易地通过递归的方式计算出最后1名士兵在初始状态(1…N)下的编号。
假设目前有n个士兵,编号为1-n。如下图:
m和n之间存在2种关系:
- m ≤ n m \le n m≤n:
如下图:
此时,枪毙士兵之后的新圆圈中编号为new的士兵在原圆圈中的编号old为:
o
l
d
=
(
n
e
w
+
m
−
1
)
%
n
+
1
old = (new + m - 1) \% n + 1
old=(new+m−1)%n+1
之所以是加上(m - 1)而不是直接加上m是因为如果:
o
l
d
=
(
n
e
w
+
m
)
%
n
old = (new + m) \% n
old=(new+m)%n
那么在
m
=
n
−
1
m=n-1
m=n−1且
n
e
w
=
1
new=1
new=1时,
(
n
e
w
+
m
)
(new + m)
(new+m)和n取余之后为0,而编号是从1开始的。
- m > n m \gt n m>n
此时,假设图中的黑色箭头绕了士兵组成了的圆圈k圈。则可以将m写成:
m
=
k
×
n
+
(
m
%
n
)
(
k
>
0
)
m = k \times n + (m \% n) (k \gt 0)
m=k×n+(m%n)(k>0)
根据第1种情况的分析,有:
o
l
d
=
(
n
e
w
+
m
−
k
×
n
)
%
n
+
1
old = (new + m - k \times n)\%n + 1
old=(new+m−k×n)%n+1
由于
(
n
e
w
+
m
−
k
×
n
)
%
n
=
=
(
n
e
w
+
m
)
%
n
(new + m - k \times n)\%n == (new + m)\%n
(new+m−k×n)%n==(new+m)%n
所以这种情况下的new和old之间的关系和第1种情况相同
综上所述,枪毙士兵之后形成的新圆圈中的任一士兵的编号new与其在旧圆圈中的编号old之间的关系是:
o
l
d
=
(
n
e
w
+
m
−
1
)
%
n
+
1
old = (new + m - 1) \% n + 1
old=(new+m−1)%n+1
由于我们开始又得到了最后剩下的士兵的编号为1,那么很容易写出递归代码如下:
int f(int n, int m){
// n为目前的士兵数量,m被枪毙士兵的报数
if(n == 1) return n;
return (f(n - 1, m) + m - 1) % n + 1;
}