约瑟夫环递推公式 f(n,k)=(f(n−1,k)+k)%n 成立是因为第一个人被杀后,剩余 n−1 人重新编号,原编号中位置 (i+k)%n 对应新编号 i,故原幸存者位置为 (f(n−1,k)+k)%n;初始条件 f(1,k)=0。

约瑟夫环的递推公式怎么来的
直接记住 f(n, k) = (f(n-1, k) + k) % n 是没用的,得理解它为什么成立。核心在于:当第一个人被杀后,剩下 n-1 个人重新编号,但新编号和旧编号之间存在确定的偏移关系——旧序列中位置 (i + k) % n 的人,在新序列里就变成第 i 个。反过来,如果知道 n-1 规模的幸存者在新编号下是 f(n-1, k),那他在原编号里就是 (f(n-1, k) + k) % n。
为什么递推能降到 O(n) 时间
暴力模拟链表或数组删除要 O(nk),而递推只做 n 次加法和取模,每次 O(1)。关键点在于:它完全跳过了“谁被删”“怎么移位”这些过程,只追踪最终答案的位置映射。注意初始条件是 f(1, k) = 0(只有 1 人时,索引为 0),不是 1。
实操建议:
- 循环从
i = 2到n,用一个变量ans累计更新:ans = (ans + k) % i - 如果题目要求 1-based 编号(即人从 1 开始报数),最后返回
ans + 1 - 注意
k可能远大于i,但(ans + k) % i没问题,C++ 中 % 对负数行为不一致,确保ans和k非负
边界与易错点:k=1 或 k 很大时怎么办
k = 1 是特例,结果恒为 n(1-based)或 n-1(0-based),但递推公式仍适用,无需单独判断;真正容易出错的是整数溢出和取模符号。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
常见错误现象:
- 用
(ans + k - 1) % i—— 这是错的,标准递推不含 -1 - 初始化
ans = 1—— 应为0(0-based) - 写成
ans = (ans + k) % (i - 1)—— 模数应是当前人数i,不是上一轮的i-1 - 当
k极大(比如 1e9)且i很小,ans + k可能溢出 int,建议用 long long 或先取模:(ans + k % i) % i(等价且安全)
能不能再快?O(1) 近似解存在但不实用
当 k 远小于 n 时,有优化版本跳过连续多轮不涉及模数变化的迭代,最坏仍是 O(k log n),但实现复杂、边界多、实际性能提升有限。对绝大多数 OJ 场景,朴素 O(n) 递推已是最优选择——代码短、无 bug、cache 友好、常数极小。
真正容易被忽略的是:数学解只适用于“每轮固定步长 k”的经典约瑟夫环;一旦改成动态步长(如 k 随轮次变)、双向删除、或带权重,就必须回归模拟或 DP,别硬套这个公式。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










