
本文介绍如何将时间复杂度从 o(b) 降至 o(n) 来解决 hackerrank 上的“movement slots”弹跳槽位问题,核心是识别状态转移中的循环节(intro + cycle),避免对超大步数 b(如 10¹²)进行暴力模拟。
本文介绍如何将时间复杂度从 o(b) 降至 o(n) 来解决 hackerrank 上的“movement slots”弹跳槽位问题,核心是识别状态转移中的循环节(intro + cycle),避免对超大步数 b(如 10¹²)进行暴力模拟。
在轮盘弹跳问题中,一个球从起始槽位 a 出发,每落在槽位 i,即按固定偏移量 slots[i] 跳转至新槽位:next = (i + slots[i]) % n。当查询次数 q 较多、且单次跳跃步数 b 高达 10¹² 时,朴素的 for _ in range(b) 模拟必然超时——其时间复杂度为 O(q·b),完全不可接受。
根本优化思路在于:状态空间有限(仅 n 个槽位),因此任意起始点出发的转移路径必在至多 n+1 步内出现重复,从而形成「入环前缀(intro)」+「循环节(cycle)」结构。一旦定位该结构,即可将大步数 b 映射到等效的小步数,实现常数级加速。
✅ 算法步骤详解
- 模拟并记录访问轨迹:从起始槽位 a 开始,逐次计算下一槽位,并用字典 visited 记录每个槽位首次出现的步数(即索引)。
- 检测循环起点:当某槽位 cur 已存在于 visited 中,说明首次重复发生——此时 visited[cur] 即为入环位置 X(intro 长度),当前步数 step 与 visited[cur] 的差值即为循环节长度 Y = step - visited[cur]。
-
分类计算结果:
- 若 b ≤ X:直接返回第 b 步的槽位(路径未入环);
- 若 b > X:等效步数为 X + (b - X) % Y,即走完前缀后,在环内走余数步。
? Python 实现(单次查询)
def final_slot_optimized(a, b, slots):
n = len(slots)
if b == 0:
return a
visited = {} # slot -> first step index
path = [] # list of slots in order of visit
cur = a
step = 0
# Detect cycle: simulate until repetition or b steps
while step <= b and cur not in visited:
visited[cur] = step
path.append(cur)
cur = (cur + slots[cur]) % n
step += 1
# Case 1: no cycle within b steps
if b < step:
return path[b]
# Case 2: cycle detected
X = visited[cur] # intro length
Y = step - X # cycle length
if b <= X:
return path[b]
else:
idx_in_cycle = (b - X) % Y
return path[X + idx_in_cycle]⚠️ 注意事项与工程建议
- 空间友好性:path 最多存储 n+1 个槽位,空间复杂度 O(n),远优于存储全部 b 步。
- 多查询优化:若需批量处理 q 组 (a, b) 查询,可对每个不同 a 预先构建其专属的 (X_a, Y_a, path_a),后续查询均摊 O(1);但注意最坏仍需 O(n) 预处理/查询。
- 取模安全:slots[i] 可为负数,Python 中 (i + slots[i]) % n 自动处理负余数,无需额外修正(如 ((i + slots[i]) % n + n) % n)。
- 边界验证:确保输入 0 ≤ a
✅ 总结
该问题本质是有向函数图(functional graph)上的路径查询:每个节点出度为 1,路径必成 ρ 形。利用循环节跳过冗余迭代,是处理超大步数模拟的经典范式。掌握此方法,不仅能破解本题,还可迁移至链表环检测、快速幂、Pollard-Rho 等众多算法场景。记住口诀:有限状态 → 必现循环 → 分段映射 → 复杂度降维。










