
本文介绍如何将时间复杂度从 o(b) 降至 o(n) 来解决大规模弹跳步数(b 可达 10¹²)下的槽位定位问题,核心是识别状态转移中的循环节并跳过重复周期。
本文介绍如何将时间复杂度从 o(b) 降至 o(n) 来解决大规模弹跳步数(b 可达 10¹²)下的槽位定位问题,核心是识别状态转移中的循环节并跳过重复周期。
在 HackerRank 的 Movement Slots 问题中,小球从起始槽位 a 出发,每次根据当前槽位 i 对应的偏移量 slots[i] 跳转至 (i + slots[i]) % n,共执行 b 次弹跳。朴素模拟需 O(b) 时间,当 b 高达 10¹² 时必然超时。但注意到:槽位总数仅为 n,而状态空间有限(仅有 n 个可能位置),因此整个弹跳路径必然在至多 n+1 步内出现重复状态,从而形成「入环前缀(prefix)」+「循环节(cycle)」的经典结构。
我们可借助 Floyd 判圈算法(快慢指针) 或更直观的 哈希表记录首次访问步数 来检测循环:
- 使用字典 visited 记录每个槽位 i 第一次被访问时的步数;
- 模拟过程中一旦遇到已访问过的槽位,即发现环:设当前步数为 step,该槽位首次出现在 visited[i] = first,则:
- 入环前缀长度 X = first
- 循环节长度 Y = step - first
据此,最终槽位计算逻辑如下:
def final_slot_optimized(a, b, slots):
n = len(slots)
if b == 0:
return a
# Step 1: Detect cycle (prefix X and period Y)
visited = {}
current = a
step = 0
while step <= b and current not in visited:
visited[current] = step
current = (current + slots[current]) % n
step += 1
# If b steps completed before cycle detected
if step > b:
return current
# Cycle detected: first occurrence at 'first', current step is 'step'
first = visited[current]
X = first
Y = step - first
# Step 2: Skip full cycles
if b <= X:
# No cycle involved
current = a
for _ in range(b):
current = (current + slots[current]) % n
return current
else:
# Jump to position after X steps, then advance (b - X) % Y more
current = a
for _ in range(X):
current = (current + slots[current]) % n
remaining = (b - X) % Y
for _ in range(remaining):
current = (current + slots[current]) % n
return current该方案最坏时间复杂度为 O(n):第一遍模拟最多 n+1 步发现环,第二遍最多 n 步完成定位。即使 b = 10¹²,也仅需约 2n 次运算,远优于原始 O(b)。
关键注意事项:
- 槽位编号为 0 到 n−1,所有模运算必须使用 % n 保证结果合法;
- slots[i] 可为负数,Python 中 (i + slots[i]) % n 自动处理负数取模(如 -1 % 5 == 4),无需额外修正;
- 若题目含多组查询(如 q 个 (a, b) 对),不可对每组单独做完整环检测——应预处理每个起始点的环信息,或采用更通用的「功能图(functional graph)」离线分析(如倍增法 / 二进制拆分),但本题单次查询下上述方法已足够高效;
- 实际提交时建议封装为 find_last_bounce(jumps, queries) 并复用优化逻辑,避免重复初始化。
通过识别确定性状态转移中的周期性,我们将一个看似不可行的大数模拟问题,转化为可稳定落地的线性算法——这正是算法优化中“洞察结构优于暴力加速”的典型体现。










