PTA(顺序表)2-13 约瑟夫环
PTA(顺序表)2-13 约瑟夫环
zhangzhang2-13 约瑟夫环
分数 9
作者 李廷元
单位 中国民用航空飞行学院
N个人围成一圈顺序编号,从1号开始按1、2、3……顺序报数,报p者退出圈外,其余的人再从1、2、3开始报数,报p的人再退出圈外,以此类推。
请按退出顺序输出每个退出人的原序号。
输入格式:
输入只有一行,包括一个整数N(1<=N<=3000)及一个整数p(1<=p<=5000)。
输出格式:
按退出顺序输出每个退出人的原序号,数据间以一个空格分隔,但行尾无空格。
输入样例:
在这里给出一组输入。例如:
1 | 7 3 |
输出样例:
1 | 3 6 2 7 5 1 4 |
我卡住的位置
1 |
|
我的超时代码
1 |
|
每次找 “下一个在圈中的人” 都要循环跳过已退出者,时间复杂度太高(O (N²))
正确代码
1 |
|
逆天太妙了
这段代码是约瑟夫环问题的高效实现,核心思路是通过直接计算退出位置 + 动态调整数组来模拟 “报数 - 退出” 过程,避免了大量无效判断,时间效率很高。我们分步骤解析逻辑:
一、初始化:用数组存储所有人的序号
1 | int* people = new int[N]; |
- 数组
people的作用:实时记录当前 “在圈中” 的人的原始序号(随着有人退出,数组会动态缩减有效长度)。 - 变量定义:
count:已退出的人数(初始 0,最终达到 N 时结束)。current:当前要退出的人的位置索引(在数组有效范围内)。remaining:当前还在圈中的人数(初始 N,每退出 1 人减 1)。
二、核心循环:每轮处理 1 个退出者,共执行 N 轮
循环条件count < N:直到所有人都退出为止。
步骤 1:计算 “下一个要退出的位置”
1 | current = (current + p - 1) % remaining; |
这是最关键的一步,用数学计算直接定位退出者,避免逐个报数:
- 原理:从当前位置开始,数
p个有效人(在圈中),第p个人就是退出者。 - 举例:假设当前remaining=5(还剩 5 人),p=3,current=0:
- 计算:
(0 + 3 - 1) % 5 = 2 % 5 = 2→ 退出者是数组中索引为 2 的人。 - 对应逻辑:从当前位置(0)开始数 3 个有效人,分别是 0→1→2,第 3 个就是索引 2。
- 计算:
- 取模
% remaining的作用:适配 “环形” 逻辑,当p超过剩余人数时自动循环(比如p=10,remaining=5,等价于数10%5=0→实际数 5 个)。
步骤 2:输出退出者的序号
1 | if (firstOutput) { |
- 直接输出数组中
current索引对应的序号(因为数组只保留 “在圈中” 的人,所以索引直接对应有效位置)。
步骤 3:移除退出者,调整数组作用:将退出者从 “有效圈” 中移除,后续报数不再考虑他。
举例:原数组
1
[1,2,3,4,5]
,退出者是索引 2(值 3):
- 循环后数组变为
[1,2,4,5,5](最后一个元素无效,但remaining会减 1,后续不再访问)。 - 实际有效部分是前
remaining-1个元素(即[1,2,4,5])。
- 循环后数组变为
步骤 4:更新计数,准备下一轮
1 | count++; // 已退出人数+1 |
三、举例验证(输入 N=7, p=3)
模拟过程:
- 初始数组:
[1,2,3,4,5,6,7],remaining=7,current=0 - 第 1 轮:
current=(0+3-1)%7=2→ 输出people[2]=3- 移除后数组有效部分:
[1,2,4,5,6,7],remaining=6
- 第 2 轮:
current=(2+3-1)%6=4%6=4→ 输出people[4]=6- 移除后数组有效部分:
[1,2,4,5,7],remaining=5
- 后续轮次以此类推,最终输出
3 6 2 7 5 1 4,与示例一致。
核心优势
- 高效性:通过数学计算直接定位退出位置,避免逐个检查 “是否在圈中”,时间复杂度 O (N²)(主要是移除元素时的数组移动),但实际执行效率远高于顺序表标记法。
- 直观性:数组始终存储当前 “在圈中” 的人,索引直接对应位置,无需处理无效标记(如 0)。


