数组中的环指从某索引出发按规则跳转后回到已访问索引形成的闭环,需满足环长≥2、所有元素同号且跳转索引合法;常用快慢指针检测,但须严格处理负数取模、符号一致性和自环排除。

什么是数组中的环?
数组中存在环,指的是从某个索引出发,按规则跳转(比如 nums[i] 的值决定下一步跳到哪个位置),最终又回到某个已访问过的索引——形成闭环。常见于「下一个索引 = (i + nums[i]) % n」或「下一个索引 = i + nums[i]」这类题设,典型如 LeetCode 457(环形数组是否存在循环)。
关键不是物理上首尾相连,而是索引跳转路径中出现重复访问,且环长 ≥ 2、所有元素同号(题目常加此限制,避免单点自环或正负抵消)。
用快慢指针检测环,但要注意符号和方向
快慢指针(Floyd’s Cycle Detection)能在线性时间、常数空间下判断环,但直接套用会出错:数组跳转不是单向链表,可能跳到负索引、越界,或正负混杂导致“伪环”。
- 必须先校验跳转目标是否合法:
next = ((i + nums[i]) % n + n) % n—— 用双重取模防负数 - 必须保证路径上所有数字同号:一旦
nums[slow]和nums[next]异号,整条路径无效,立即放弃该起点 - 快指针每次走两步,但每步都需重新检查符号和合法性;若任意一步失败,当前起点无有效环
- 环长判断不能只看快慢相遇:要从相遇点再走一圈计数,确认步数 ≥ 2
为什么不能用 visited 数组暴力标记?
可以,但效率低且容易误判。例如:
立即学习“C++免费学习笔记(深入)”;
- 用布尔数组
visited[i]标记全局访问,会导致不同起点的路径互相干扰——某次失败的搜索把中间点标为 true,后续真正成环的路径就被跳过 - 正确做法是每个起点独立搜索,用局部集合或临时标记(如乘以一个大数做“已在此轮访问”标记),但需注意恢复原值或使用额外空间
- 更稳妥的是:对每个未启动的起点,用一个
unordered_set记录本轮访问过的索引,遇到重复即成环;但空间 O(n),不满足最优解要求 - 快慢指针仍是首选,前提是严格守卫符号一致性和索引合法性
实际写的时候最容易漏掉这三点
很多人写出逻辑看似完整,却在测试用例上失败,问题往往出在:
- 没处理负数取模:
-1 % 5在 C++ 中是-1,不是4,必须写成((i + nums[i]) % n + n) % n - 忽略“环内所有数必须同号”的约束:快慢指针绕了一圈回来,但中途有正有负,这不是题目定义的有效环
- 把自环(
i == next)当有效环:比如nums = [1, -1],从 0 出发跳到 1,再跳回 0,看起来是环,但若nums[0] == 1且n == 2,实际是0 → 1 → 0,长度为 2,合法;而[2, -1, 1, 2, 2]中的[2]自环(0 → (0+2)%5=2 → (2+1)%5=3 → ...)需单独排除
环判断本身不难,难在题干隐含条件和 C++ 取模行为的组合陷阱。动手前先手推两组边界数据,比补运行时错误更快。


















