std::list 模拟约瑟夫环最直观,其 erase 和迭代器自增天然适配“跳 m-1 个、删第 m 个”,无需手动处理下标越界,初始化可用 for (int i = 1; i

用 std::list 模拟删除过程最直观
约瑟夫环本质是循环链表删节点,std::list 的 erase 和迭代器自增天然适配“跳 m-1 个、删第 m 个”的逻辑,不用手动管理下标越界。
- 初始化时用
for (int i = 1; i ,别从 0 开始——题干通常编号从 1 起 - 迭代器走到末尾后要重置:
if (it == lst.end()) it = lst.begin();,漏这句会崩溃 - 删完一个元素后,
erase返回下一个有效迭代器,但你得先走m-1步再删,所以建议删前用std::next(it, m-1)计算目标位置,再处理循环绕回
josephus(n, m) 递推公式怎么写对
递推式 f(1)=0,f(k)=(f(k-1)+m)%k 是基于 0-index 的结果,直接套用会比题目要求的编号小 1。
- 如果题目说“1~n 编号,报到 m 出列”,最终答案必须加 1:
return f(n) + 1 -
m可能大于当前人数k,但取模已隐含处理,无需提前m %= k——反而错,因为递推依赖原始m - 递推适合大
n(比如 1e6),但要注意int溢出:若m很大且n大,f(k-1)+m可能超INT_MAX,改用long long
模拟法 vs 递推法:什么时候选哪个
两者时间复杂度差一个数量级,但不是无脑选快的。
- 需要输出完整出列顺序?只能模拟,递推只给最后幸存者
-
n是 1e5 但m是 2?用递推,O(n)稳定;若m接近n,模拟中std::list::erase平均仍是O(1),总O(n),差别不大 - 面试里写递推容易被追问“为什么是
(f(k-1)+m)%k”,得能讲清:上一轮幸存者在新编号体系里的偏移量,加上步长,再映射回本轮编号
边界情况一碰就错
输入 n=1 或 m=1 时,所有实现都可能崩或返回错值。
立即学习“C++免费学习笔记(深入)”;
-
n == 1:无论m是多少,答案就是 1,递推里f(1)=0后加 1 刚好,但模拟代码若没单独判断,list遍历时可能空跑 -
m == 1:每轮删第一个,结果一定是n,递推公式此时退化为f(k) = (f(k-1)+1)%k,手算几轮就能验证;但有人写成(f(k-1)+m-1)%k就全错了 - 测试一定要覆盖
n=1, m=1、n=5, m=2(经典 3)、n=7, m=3这三组,答案分别是 1、3、4
递推的数学形式看着干净,但下标偏移和初始条件错一位,结果就全偏;模拟看似啰嗦,反而容错高。实际写的时候,先跑通小样例,再换大数测性能。










