检测链表是否有环, 动画演示, Floyd判圈算法扩展应用
力扣原题链接: 141. 环形链表 - 力扣(LeetCode)
哈希表
检测环形链表, 直观的思路就是使用哈希表, 遍历这个链表, 将访问过的节点加入到哈希表中, 如果遍历过程中发现节点已经存在于哈希表中, 则说明链表有环.
复杂度分析:
- 时间复杂度: O(N), 最坏情况下需要遍历所有节点.
- 空间复杂度: O(N), 最坏情况下每个节点都会加入到哈希表.
Floyd判圈算法
另一种更高效的算法是Floyd判圈算法, 又称龟兔赛跑算法, 该算法由计算机科学家罗伯特·弗洛伊德(Robert W. Floyd)提出, 其核心思想是通过两个指针以不同速度遍历链表来判断是否存在环, 并可以进一步确定环的起点和长度.
算法步骤
初始化两个指针:
- 慢指针: 每次移动一步
- 快指针: 每次移动两步
两个指针同时从起点出发, 按照各自的速度移动.
判断是否有环:
- 如果快指针会先到达终点, 则说明链表无环
- 如果快慢指针相遇, 则说明链表有环
动画演示
欢迎关注公众号: 算法铁金库
持续更新数据结构与算法, 动画演示, 理解算法快人一步~
Java代码:
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null) {
return false;
}
ListNode slow = head;
ListNode fast = head;
do {
slow = slow.next;
if (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
} else {
return false;
}
} while (slow != fast);
return true;
}
}
复杂度分析:
- 时间复杂度: O(N), 慢指针对每个节点至多访问一次, 快指针访问的节点个数不超过慢指针的2倍.
- 空间复杂度: O(1), 只需要两个额外指针.
思考: 为什么当链表有环, 快慢指针一定能相遇?
如果存在环, 快指针每次比慢指针多走一步, 也就是说距离每次减1, 所以最终一定能相遇.
扩展应用
确定环的起点
当快慢指针首次相遇后, 将其中一个指针移回起点(如慢指针), 然后两个指针改为每次均移动一步. 当它们再次相遇时, 相遇点即为环的起点.
证明:
用a表示链表头到环起点的距离
用L表示环的长度
第一阶段: 寻找相遇点
- 当慢指针走了a步到达环起点时, 快指针已经走了2a步
- 快指针在环中的位置是 (2a - a) % L = a % L (因为前a步用来走到环起点)
- 此时快指针需要追赶慢指针, 相对距离是 L - a % L
- 由于快指针每次比慢指针多走1步, 经过 L - a % L 步后两者相遇
- L - a % L 就是慢指针在环中移动的长度, 也就是相遇位置距环起点的距离
第二阶段: 确定环起点
- 将慢指针移回链表头部, 快指针不动, 接下来两个指针每次各走1步
- 慢指针走了a步到达环起点, 同时快指针也走了a步, 在环中的位置为 L - a % L + a
- 而 (L - a % L + a) % L = 0, 也就是说该位置就是环起点.
证毕.
计算环的长度
在确定有环后, 固定一个指针, 另一个指针继续移动并计数, 直到再次相遇, 计数结果即为环的长度.