约瑟夫环的三种解法
1|0什么是约瑟夫环问题
-
已知 n 个人(以编号1,2,3 … n 分别表示)围成一圈。从编号为 1 的人开始报数,数到 m 的那个人出列;他的下一个人又从 1 开始报数,数到 m 的那个人又出列;依此规律重复下去,直到最后剩下一个人。要求找出最后出列的人的编号
可能有些同学看到的不是从编号为 1 的人开始报数,但我想说,不管从编号为几的人开始报数,其实都可以将这个第一个开始报数的人的编号看作是 1,在得出最后出列的人的编号之后,我们很容易就可以将其转为从编号为 1 的人开始报数的情况下,最后一个出列的人的编号
2|0用数组解决
- 用数组来解决,应该是很多第一次接触到这个问题的人最容易想到的一种方式,思想很简单,但实现起来需要考虑的地方还是很多的
-
然后不停着遍历数组,对于被选中的编号,我们就做一个标记,例如编号 arr[2] = 3 被选中了,那么我们可以做一个标记,例如让 arr[2] = -1,来表示 arr[2] 存放的编号已经出局了
-
然后就按照这种方法,不停着遍历数组,不停着做标记,直到数组中只有一个元素是非 -1 的,这样,剩下的那个元素就是我们要找的元素了。演示如下
-
编码实现这里就不写了,本文的重点在第三种方法
-
这种做法的时间复杂度是 O(nm), 空间复杂度是 O(n)
3|0用环形链表解决
-
用链表来处理其实和上面处理的思路差不多,只是用链表来处理的时候,对于被选中的编号,不再是做标记,而是直接移除,因为从链表移除一个元素的时间复杂度很低,为 O(1)
-
当然,上面数组的方法你也可以采用移除的方式,不过数组移除的时间复杂度为 O(n)
4|0用递归解决
-
为了解决上面两种方法的效率问题,我们可以从数学角度对这个问题进行分析,找出其中的规律,然后使用递归实现
-
为了方便导出递归公式,这里先对问题做个简短的定义
-
问题定义:有 n 个人,编号为 1,2,……,n,从编号为 1 的人开始,从 1 开始依次报数,每报到 m 时,该人出列,求最后出列的人的编号
-
我们首先定义一个函数 f(n,m)f(n,m) f(n,m)f(n,m),它表示的是 nn nn 个人报数,每报到 mm mm 的人出列,最后出列的人的编号。显然问题的最小规模就是当 n = 1 时,此时 f(1,m)=1f(1,m)=1 f(1,m) = 1f(1,m)=1
-
f(n−1,m)f(n−1,m) f(n-1,m)f(n−1,m) 表示的是 n−1n−1 n-1n−1 个人报数,每报到 mm mm 的人出列,最后出列的人的编号
-
这里先把结论给出来:f(n,m)=(f(n−1,m)+m)%nf(n,m)=(f(n−1,m)+m)%n f(n,m)=(f(n-1,m)+m) \% nf(n,m)=(f(n−1,m)+m)%n
-
这个公式是如何推导出来的呢?我们已经知道,f(n−1,m)f(n−1,m) f(n-1,m)f(n−1,m) 表示的是总人数为 n - 1 个时,最后出列的人的编号,假如暂不考虑数组越界的问题,那么当总人数为 n 时,最后出列的人的编号就是 f(n−1,m)+mf(n−1,m)+m f(n-1,m)+mf(n−1,m)+m 。为了防止数组越界,所以我们对 n 取余数
有些人看到这个解释可能很模糊,其实很简单。我们这样想:反正总共是 n 个人,后一次出列的人的编号肯定是比前一次出列的人的编号大 mm mm 的,同时为了兼顾数组越界的情况,我们需要对 nn nn 取余数
-
下面给出代码实现:
-
如果要用递归,就是这样写
- 这里没有写成 return (f(n - 1, m) + m ) % n,主要是因为编号是从 1 开始的,而不是从 0 开始的
- 另可参考这里
-
如果编号从 0 开始,则最后的返回值表示的是数组的下标,
要想得到编号,最终的返回结果还需要加 1
,代码如下
https://blog.csdn.net/WinstonLau/article/details/99701837
__EOF__

本文链接:https://www.cnblogs.com/treasury/p/12990821.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?