环形链表的约瑟夫问题(进阶)

环形链表的约瑟夫问题(进阶)

题目描述

据说著名犹太历史学家 Josephus 有过以下故事:在罗马人占领乔塔帕特后,39 个犹太人与 Josephus 及他的朋友躲到一个洞中,39 个犹太人决定宁愿死也不要被敌人抓到,于是决定了一种自杀方式,41 个人排成一个圆圈,由第 1 个人开始报数,报数到 3 的人就自杀,然后再由下一个人重新报 1,报数到 3 的人再自杀,这样依次下去,直到剩下最后一个人时,那个人可以自由选择自己的命运。这就是著名的约瑟夫问题。现在请用单向环形链表得出最终存活的人的编号。

输入描述:

一行两个整数 n,m,n 表示链表的长度,m 表示每报数到 m 就自杀。

输出描述:

输出最后存活的人的编号(编号从 1 开始到 n)。

示例1
输入
5 2
输出
3
备注:

1 ≤ n , m ≤ 5 ∗ 1 0 6 1 \leq n,m \leq 5*10^6 1n,m5106


题解:

观察一下数据,在前面一篇 环形链表的约瑟夫问题 中,数据范围较小,可以以 O ( n ∗ m ) O(n*m) O(nm) 的时间复杂度进行模拟,但是本题的数据范围意味着我们需要考虑 O ( n ) O(n) O(n)时间复杂度的算法。之前的模拟是因为我们不知道到底哪个会活下来,所以通过不停的删除来淘汰无关节点,直到剩下一个节点。如果我们可以不用一直删除的方式,而是直接通过 n 和 m 计算哪个节点会活下来,然后找到该节点的编号即可。那么怎么找到呢?考虑如下问题:

1个人:fun(1) = 1;

2个人:fun(2);

n-1个人:fun(n-1);

n个人:fun(n)。

我们已经知道 fun(1) = 1 ,如果能确定 fun(i-1) 和 fun(i) 之间的关系,就可以通过递归求出 fun(n) 了。

假设 n 和 m 分别为 5 和 3,假设编号为 A 的人报的是 B ,则 A 和 B 的对应关系如下:

AB
11
22
33
41
52

上面假设了 n 和 m 分别是 3 和 5 ,第一个被杀掉人的编号为 3 ,在杀掉 3 后,对剩下的人重新编号,如下:

A_oldA_new
13
24
3x
41
52

x 表示人已经被杀掉,从上表中可以看到,编号之间的关系为:old = (new + 3 - 1) % 5 + 1,改写一下公式就是:old = (new + m - 1) % i + 1,i 表示有 i 个人。至此,我们找到了 fun(i-1)->new 和 fun(i)->old 之间的关系。

那么剩下的就是写代码了,由于每个状态只和前一个状态有关,可以通过循环实现,不需要用递归,见代码:

代码:
#include <cstdio>

using namespace std;

int main(void) {
    int n, m;
    scanf("%d%d", &n, &m);
    int now = 0;
    for (int i = 2; i <= n; ++i) now = (now + m) % i;
    printf("%d\n", now + 1);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值