问题描述
0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
该问题是《剑指 Offer》中的第62题,在 LeetCode 中的难度为简单。
剑指 Offer 62. 圆圈中最后剩下的数字
解决思路
数学建模
设在 0 0 0 到 n − 1 n - 1 n−1 这 n n n 个数字组成的圆圈中,从下标为 s s s 的位置开始数,每次数到第 m m m 个数字并将其删除,剩下的最后一个数字为 f ( n , m , s ) f(n, m, s) f(n,m,s) 。
因此,该题目要求的就是 f ( n , m , 0 ) f(n, m, 0) f(n,m,0) 。
递推过程
要解决该问题,首先要意识到,原问题的答案可以通过子问题的答案求得。
也就是说,如果我们能知道
f
(
n
−
1
,
m
,
0
)
f(n - 1, m, 0)
f(n−1,m,0) ,就有办法算出
f
(
n
,
m
,
0
)
f(n, m, 0)
f(n,m,0) 。
这样一来,就可以用类似动态规划的方式,递归地求得问题的答案(当然,代码实现最好是用迭代的方式)。
第一步
首先,要找到
f
(
n
,
m
,
0
)
f(n, m, 0)
f(n,m,0) 与
f
(
n
−
1
,
m
,
0
)
f(n - 1, m, 0)
f(n−1,m,0) 之间的关系,先思考一个问题:
s
s
s 取多少,才能让
f
(
n
,
m
,
s
)
=
f
(
n
−
1
,
m
,
0
)
f(n, m, s) = f(n - 1, m, 0)
f(n,m,s)=f(n−1,m,0) ?
即,在 0 0 0 到 n − 1 n - 1 n−1 这 n n n 个数字组成的圆圈中,从哪个位置( s s s )开始数,才能让第 m m m 个数字正好是 n − 1 n - 1 n−1 (即最后一个数),从而使得这时的情况就正好成为了 f ( n − 1 , m , 0 ) f(n - 1, m, 0) f(n−1,m,0) 的初始情况。
简单思考即可知,从下标
n
−
m
n - m
n−m 处开始数,第
m
m
m 个数字就正好是
n
−
1
n - 1
n−1 。
因此可得,
f
(
n
,
m
,
n
−
m
)
=
f
(
n
−
1
,
m
,
0
)
f(n, m, n - m) = f(n - 1, m, 0)
f(n,m,n−m)=f(n−1,m,0) 。
第二步
已经知道了 f ( n , m , n − m ) f(n, m, n - m) f(n,m,n−m) 和 f ( n − 1 , m , 0 ) f(n - 1, m, 0) f(n−1,m,0) 的关系(相等),只需要再知道 f ( n , m , 0 ) f(n, m, 0) f(n,m,0) 和 f ( n , m , n − m ) f(n, m, n - m) f(n,m,n−m) 的关系,不就知道了 f ( n , m , 0 ) f(n, m, 0) f(n,m,0) 和 f ( n − 1 , m , 0 ) f(n - 1, m, 0) f(n−1,m,0) 的关系了吗。
很容易发现,
f
(
n
,
m
,
s
−
1
)
=
f
(
n
,
m
,
s
)
−
1
f(n, m, s - 1) = f(n, m, s) - 1
f(n,m,s−1)=f(n,m,s)−1 。
因为开始数的位置每向左移一个,剩下的最后一个数字的位置也会向左移一个。
故可得,
f
(
n
,
m
,
s
−
s
)
=
f
(
n
,
m
,
s
)
−
s
f(n, m, s - s) = f(n, m, s) - s
f(n,m,s−s)=f(n,m,s)−s ,即
f
(
n
,
m
,
0
)
=
f
(
n
,
m
,
s
)
−
s
f(n, m, 0) = f(n, m, s) - s
f(n,m,0)=f(n,m,s)−s 。
当然,
f
(
n
,
m
,
s
)
−
s
f(n, m, s) - s
f(n,m,s)−s 可能是负数,所以应该完善为
f
(
n
,
m
,
0
)
=
[
f
(
n
,
m
,
s
)
−
s
]
%
n
f(n, m, 0) = [f(n, m, s) - s] \% n
f(n,m,0)=[f(n,m,s)−s]%n 。
所以, f ( n , m , 0 ) f(n, m, 0) f(n,m,0) 和 f ( n , m , n − m ) f(n, m, n - m) f(n,m,n−m) 的关系为: f ( n , m , 0 ) = [ f ( n , m , n − m ) + m − n ] % n f(n, m, 0) = [f(n, m, n - m) + m - n] \% n f(n,m,0)=[f(n,m,n−m)+m−n]%n 。
第三步
现在已知
f
(
n
,
m
,
n
−
m
)
=
f
(
n
−
1
,
m
,
0
)
f(n, m, n - m) = f(n - 1, m, 0)
f(n,m,n−m)=f(n−1,m,0) 和
f
(
n
,
m
,
0
)
=
[
f
(
n
,
m
,
n
−
m
)
+
m
−
n
]
%
n
f(n, m, 0) = [f(n, m, n - m) + m - n] \% n
f(n,m,0)=[f(n,m,n−m)+m−n]%n 。
便可推导出:
f
(
n
,
m
,
0
)
=
[
f
(
n
−
1
,
m
,
0
)
+
m
−
n
]
%
n
=
{
[
f
(
n
−
1
,
m
,
0
)
+
m
]
−
n
}
%
n
=
{
[
f
(
n
−
1
,
m
,
0
)
+
m
]
%
n
−
n
%
n
}
%
n
=
{
[
f
(
n
−
1
,
m
,
0
)
+
m
]
%
n
−
0
}
%
n
=
{
[
f
(
n
−
1
,
m
,
0
)
+
m
]
%
n
}
%
n
=
[
f
(
n
−
1
,
m
,
0
)
+
m
]
%
n
f(n, m, 0) = [ f(n - 1, m, 0) + m - n ] \% n \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \,\, = \{[ f(n - 1, m, 0) + m ] - n \} \% n \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \,\, = \{[ f(n - 1, m, 0) + m ] \% n - n \% n \} \% n \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \,\, = \{[ f(n - 1, m, 0) + m ] \% n - 0 \} \% n \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \, = \{[ f(n - 1, m, 0) + m ] \% n \} \% n \\ \ \ \ \ \ \ \ \ \ \ \,\, = [ f(n - 1, m, 0) + m ] \% n
f(n,m,0)=[f(n−1,m,0)+m−n]%n ={[f(n−1,m,0)+m]−n}%n ={[f(n−1,m,0)+m]%n−n%n}%n ={[f(n−1,m,0)+m]%n−0}%n ={[f(n−1,m,0)+m]%n}%n =[f(n−1,m,0)+m]%n
代码实现
有了
f
(
n
,
m
,
0
)
f(n, m, 0)
f(n,m,0) 和
f
(
n
−
1
,
m
,
0
)
f(n - 1, m, 0)
f(n−1,m,0) 的关系:
f
(
n
,
m
,
0
)
=
[
f
(
n
−
1
,
m
,
0
)
+
m
]
%
n
f(n, m, 0) = [ f(n - 1, m, 0) + m ] \% n
f(n,m,0)=[f(n−1,m,0)+m]%n 。
我们就可以用简单的三行 Java 代码解决这个问题了。
public int lastRemaining(int n, int m) {
int f = 0;
for (int i = 2; i <= n; i++) f = (f + m) % i;
return f;
}
该算法的时间复杂度为 O ( n ) O(n) O(n) ,空间复杂度为 O ( 1 ) O(1) O(1) 。