题目描述
在0,1,…,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字。
求出这个圆圈里剩下的最后一个数字。
样例
输入:n=5 , m=3
输出:3
思路一 用环形链表模拟圆圈
本题就是有名的约瑟夫环问题。我们可以环形列表来模拟,每次从这个列表中删除第
m
m
m个元素,一直到列表最后剩下一个元素为止。
考虑用STL中std::list来模拟这个环形列表,由于list并不是一个环形的结构,因此每次跌代器扫描到列表末尾的时候,要把迭代器移到列表的头部。这样就是按照一个圆圈的顺序来遍历这个列表了。
时间复杂度是
O
(
m
n
)
O(mn)
O(mn)。
class Solution {
public:
int lastRemaining(int n, int m){
if(n < 1 || m < 1)
return -1;
list<int> nums;
for(int i = 0; i < n; ++i)
nums.push_back(i);
list<int>::iterator cur = nums.begin();
while(nums.size() > 1){
for(int i = 1; i < m; ++i){
++cur;
if(cur == nums.end())
cur = nums.begin();
}
list<int>::iterator next = ++cur;
if(next == nums.end())
next = nums.begin();
--cur;
nums.erase(cur);
cur = next;
}
return *(cur);
}
};
思路二 公式推导
首先定义最初的 n n n个数字 ( 0 , 1 , … , n − 1 ) (0,1,…,n-1) (0,1,…,n−1)中最后剩下的数字是关于 n n n和 m m m的方程为 f ( n , m ) f(n,m) f(n,m)。
在这
n
n
n个数字中,第一个被删除的数字是
(
m
−
1
)
(m-1)%n
(m−1),为简单起见记为
k
k
k,那么删除
k
k
k之后的剩下
n
−
1
n-1
n−1的数字为
0
,
1
,
…
,
k
−
1
,
k
+
1
,
…
,
n
−
1
0,1,…,k-1,k+1,…,n-1
0,1,…,k−1,k+1,…,n−1,并且下一个开始计数的数字是
k
+
1
k+1
k+1。相当于在剩下的序列中,
k
+
1
k+1
k+1排到最前面,从而形成序列
k
+
1
,
…
,
n
−
1
,
0
,
…
k
−
1
k+1,…,n-1,0,…k-1
k+1,…,n−1,0,…k−1。该序列最后剩下的数字也应该是关于
n
n
n和
m
m
m的函数。由于这个序列的规律和前面最初的序列不一样(最初的序列是从0开始的连续序列),因此该函数不同于前面函数,记为
f
′
(
n
−
1
,
m
)
f'(n-1,m)
f′(n−1,m)。最初序列最后剩下的数字
f
(
n
,
m
)
f(n,m)
f(n,m)一定是剩下序列的最后剩下数字
f
′
(
n
−
1
,
m
)
f'(n-1,m)
f′(n−1,m),所以
f
(
n
,
m
)
=
f
′
(
n
−
1
,
m
)
f(n,m)=f'(n-1,m)
f(n,m)=f′(n−1,m)。
接下来我们把剩下的的这
n
−
1
n-1
n−1个数字的序列
k
+
1
,
…
,
n
−
1
,
0
,
…
k
−
1
k+1,…,n-1,0,…k-1
k+1,…,n−1,0,…k−1作一个映射,映射的结果是形成一个从
0
0
0到
n
−
2
n-2
n−2的序列:
k+1 -> 0
k+2 -> 1
…
n-1 -> n-k-2
0 -> n-k-1
…
k-1 -> n-2
把映射定义为 p p p,则 p ( x ) = ( x − k − 1 ) % n p(x)= (x-k-1)\%n p(x)=(x−k−1)%n,即如果映射前的数字是 x x x,则映射后的数字是 ( x − k − 1 ) % n (x-k-1)\%n (x−k−1)%n。对应的逆映射是 p − 1 ( x ) = ( x + k + 1 ) % n p-1(x)=(x+k+1)\%n p−1(x)=(x+k+1)%n。
由于映射之后的序列和最初的序列有同样的形式,都是从 0 0 0开始的连续序列,因此仍然可以用函数 f f f来表示,记为 f ( n − 1 , m ) f(n-1,m) f(n−1,m)。根据我们的映射规则,映射之前的序列最后剩下的数字 f ′ ( n − 1 , m ) = p − 1 [ f ( n − 1 , m ) ] = [ f ( n − 1 , m ) + k + 1 ] % n f'(n-1,m)= p-1 [f(n-1,m)]=[f(n-1,m)+k+1]\%n f′(n−1,m)=p−1[f(n−1,m)]=[f(n−1,m)+k+1]%n。把 k = ( m − 1 ) % n k=(m-1)\%n k=(m−1)%n代入得到 f ( n , m ) = f ′ ( n − 1 , m ) = [ f ( n − 1 , m ) + m ] % n f(n,m)=f'(n-1,m)=[f(n-1,m)+m]\%n f(n,m)=f′(n−1,m)=[f(n−1,m)+m]%n。
经过上面复杂的分析,我们终于找到一个递归的公式。要得到
n
n
n个数字的序列的最后剩下的数字,只需要得到
n
−
1
n-1
n−1个数字的序列的最后剩下的数字,并可以依此类推。当
n
=
1
n=1
n=1时,也就是序列中开始只有一个数字
0
0
0,那么很显然最后剩下的数字就是
0
0
0。因此有递推公式:
f
(
n
,
m
)
=
{
0
n=1
[
f
(
n
−
1
,
m
)
+
m
]
%
n
n>1
f(n,m)= \begin{cases} 0& \text{n=1}\\ [f(n-1, m) +m] \% n& \text{n>1} \end{cases}
f(n,m)={0[f(n−1,m)+m]%nn=1n>1
这个公式使用递归和迭代都很容易实现,时间复杂度为O(n),下面是基于递归实现的代码
class Solution {
public:
int lastRemaining(int n, int m){
if(n < 1 || m < 1)
return -1;
if(n == 1)
return 0;
return (lastRemaining(n-1, m) + m) % n;
}
};