环形链表问题
- 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
- 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
- 说明:不允许修改给定的链表。
- 进阶:你是否可以使用 O(1) 空间解决此题?
使用集合解决问题
一旦链表中出现了环,那么在我们循环遍历这个链表的过程中,在第二次进入环时,就会遍历到已经遇到过的结点,那么简单来说就是判断这个在遍历这个链表结点的过程中是否出现重复,如果存在重复就有环。
看到重复二字,我们就会想到使用集合来处理数据,创建一个哈希表,将结点依次存放到哈希表中,如果当前结点已经存在于哈希表中,则称它为入环的第一个结点,证明该链表有环。
具体代码
public ListNode detectCycle(ListNode head) {
ListNode pos=head;
//存放节点的哈希表
Set<ListNode> hashTable=new HashSet<>();
//遍历链表
while(pos!=null){
//判断当前结是否已经存在与集合中,是则有环,当前结点为入环结点
if(hashTable.contains(pos)){
return pos;
}else {
//不存在,加入集合中
hashTable.add(pos);
}
//向下一个结点移动
pos=pos.next;
}
//没环返回null
return null;
}
时间复杂度O(n),空间复杂度O(n)
使用指针解决问题
使用两个指针,一个指针移动速度快一点,一个指针移动速度慢一点,如果存在环的话它们一定会相遇。其实就是一个追击问题,下面假设有环来举例分析。
|AB|=X 、|BP|=Y 、|PB|=Z
相遇问题:
a,b两个兔子赛跑,a的速度是b的两倍,都从A点出发,如果有环的话它们就会相遇,假设P点是它们的相遇点,B点是入环点。问题是找到入环点P点的位置,也就是找到X的长度,那么现在的问题就是X的长度为多少?
(1)首先需要判断b在入圈后第几圈与a相遇
- 如果a,b同时入圈,a的速度是b的两倍,那么b会在跑完第一圈时与a跑完第二圈时相遇
- 而题中是a先入圈,那么b一定是在第一圈与a相遇
(2)通过速度构建等式
- b跑过的路程为:X+Y
- a跑过的路程为:X+Y+n(Y+Z) a跑了多少圈不确定,设跑了n圈
因为a的速度是b的两倍,所以相同时间下,路程也是b的两倍
2(X+Y)=X+Y+n(Y+Z) => X=nZ+(n-1)Y(n>=1)
(3)寻找入环点P点的位置
由等式可以得出X的长度是Z的长度加上m个完整环的长度
所以:可以假设两个兔子分别同时从A和P以同样的速度出发,它们相遇的点就是B点
将问题解析过程放入代码中
- 需要两个指针slow和fast,都指向头结点
- 不断移动两个指针,fast一次移动两个位置,slow一次移动一个位置,如果存在fast等于slow,存在环,寻找入环结点
- 重新定义两指针indexA和indexB,一个指针指向相遇结点,一个指针指向头结点
- 不断移动两个指针,每次都移动一个位置,当indexA等于indexB时该节点就是入环结点
整体代码
public ListNode detectCycle(ListNode head) {
//定义两个指针,一快一慢,初始都指向头结点
ListNode slow=head;
ListNode fast=head;
//开始移动指针
while(fast!=null&&fast.next!=null){
//慢指针每次移动一步
slow=slow.next;
//快指针每次移动两步
fast= fast.next.next;
//当两个指针指向同一个结点时,相遇,则有环,找入口结点
if(slow==fast){
//定义两个指针,一个指向头,一个指向相遇点
ListNode indexA=head;
ListNode indexB=slow;
//两指针每次同时移动一个单位,相遇时的结点就是入口结点
while(indexA!=indexB){
indexA=indexA.next;
indexB=indexB.next;
}
//返回入口结点
return indexA;
}
}
//不存在环,返回null
return null;
}
时间复杂度O(n),空间复杂度O(1)