问题描述(难度中等)
Given a linked list, return the node where the cycle begins. If there is no cycle, return null
.
To represent a cycle in the given linked list, we use an integer pos
which represents the position (0-indexed) in the linked list where tail connects to. If pos
is -1
, then there is no cycle in the linked list.
Note: Do not modify the linked list.
Example 1:
1 | Input: head = [3,2,0,-4], pos = 1 |
Example 2:
1 | Input: head = [1,2], pos = 0 |
Example 3:
1 | Input: head = [1], pos = -1 |
方法一:用HashSet检查重复
这里将每个节点看作独立的对象,散列到HashSet中,如果出现了重复节点,就直接返回当前的节点,那么该节点就是环的起点。这种方法比较直观,当然需要消耗n的空间复杂度。穿插记录下这里的每个节点都会根据hashCode去散列到数组不同的位置,我们没有重写hashCode和equals方法,那么默认每个object都有不同的hashCode,并且eqauls方法也只返回引用的比较结果。Effective Java中对对象重写equal方法有个规范,需要同时重写hashCode方法,主要为了避免用当前类对象作为Hash这种数据结构的key的时候会导致不一致,equals相等但是hashCode不想等,导致逻辑上认为相等的一个对象被散列到两个链表上。
方法一代码
1 | private HashSet<ListNode> hashSet=new HashSet<>(); |
看起来逻辑是非常清晰的,实现非常的简单。
方法二:快慢指针遍历
方法一虽然实现比较简单但是需要n的空间复杂度,在判断链表是否有环的练习题我们同样可以用快慢指针,返回相遇点,但是这里需要寻找环的起点,这个自己没有想到好的solution,从disscuss中发现了一些宝藏男孩。这里我们简单推理重现一下。
这里假设我们慢指针以一步的步长往前走,快指针两步走,最终相遇在Meet Pointer的位置,慢指针走了S的距离,快指针走了2S距离,根据图中可以推导出A=C+(N-1)*loop,也就是说设置两个起点一个从链表头部开始,一个从Meet Pointer开始,分别往下遍历,最终会在Circle Start Pointer相遇。整个算法的过程是第一次循环找到Meet Pointer,第二次循环找到Circle Start Pointer。
方法二代码
1 | public ListNode detectCycleUsingPointer(ListNode head){ |
总结
第一种方式较为直观,实现上较为简单,但是需要占用n的空间复杂度。第二种时间空间效率更高,但是实现上一开始比较难想到。