圆圈中最后剩下的数字(约瑟夫环问题)

问题描述

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 n1 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(n1,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(n1,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(n1,m,0)

即,在 0 0 0 n − 1 n - 1 n1 n n n 个数字组成的圆圈中,从哪个位置( s s s )开始数,才能让第 m m m 个数字正好是 n − 1 n - 1 n1 (即最后一个数),从而使得这时的情况就正好成为了 f ( n − 1 , m , 0 ) f(n - 1, m, 0) f(n1,m,0) 的初始情况

简单思考即可知,从下标 n − m n - m nm 处开始数,第 m m m 个数字就正好是 n − 1 n - 1 n1
因此可得, f ( n , m , n − m ) = f ( n − 1 , m , 0 ) f(n, m, n - m) = f(n - 1, m, 0) f(n,m,nm)=f(n1,m,0)

第二步

已经知道了 f ( n , m , n − m ) f(n, m, n - m) f(n,m,nm) f ( n − 1 , m , 0 ) f(n - 1, m, 0) f(n1,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,nm) 的关系,不就知道了 f ( n , m , 0 ) f(n, m, 0) f(n,m,0) f ( n − 1 , m , 0 ) f(n - 1, m, 0) f(n1,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,s1)=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,ss)=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,nm) 的关系为: 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,nm)+mn]%n

第三步

现在已知 f ( n , m , n − m ) = f ( n − 1 , m , 0 ) f(n, m, n - m) = f(n - 1, m, 0) f(n,m,nm)=f(n1,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,nm)+mn]%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(n1,m,0)+mn]%n                     ={[f(n1,m,0)+m]n}%n                                 ={[f(n1,m,0)+m]%nn%n}%n                           ={[f(n1,m,0)+m]%n0}%n                     ={[f(n1,m,0)+m]%n}%n          =[f(n1,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(n1,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(n1,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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZBH4444

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值