这是一个比较基本的链表问题。总结一下,可以扩展为以下几类问题:
- 判断链表中是否有环
- 求链表中环的长度
- 求环的入口节点的位置
- 带环链表的长度
解法:
- 判断是否有环:
- 可以利用快慢指针追赶的方式,即设定两个指针,slow、fast,slow指针每次走一步,fast指针每次走两步,如果存在环,则两个指针会相遇,即slow.val==fast.val;如果不存在环,则fast指针会先于slow到达链表结尾,此时,fast 应该为null,循环结束。
- 为什么有环的情况下二者一定会相遇:
- 因为fast会先于slow进入环,在slow进入环之后,可以把slow看作在前面,如果slow在前面,fast在后面每次循环都会向slow靠近1,所以一定会相遇,而不会出现fast直接跳过slow的情况。
具体代码如下:
定义ListNode数据结构
public class ListNode {
public int val;
public ListNode next;
public ListNode(int x) {
val = x;
}
/**
* 判断单向链表是否有环
* @param pHead
* @return
*/
public boolean HasLoopInListNode(ListNode pHead){
if(pHead == null ||pHead.next ==null){
return false;
}
ListNode slow = pHead;
ListNode fast = pHead;
while(slow !=null&&fast.next !=null){
//快慢指针分别访问下一节点
slow = slow.next;
fast = fast.next.next;
if(fast.val == slow.val)
return true;
}
return false;
}
- 求环的长度
- 对于求环的长度,可以在第一问的基础上进行进一步的操作,即记录下快慢指针slow、fast首次相遇的节点P,然后slow从P点开始,再次访问环中下一节点,而fast停留在碰撞点P,直到slow再一次到达碰撞点P,slow走过的步数即为环的长度。
/**
* 求环的长度
* @param pHead
* @return
*/
public int LengthOfLoopListNode(ListNode pHead){
if(pHead == null ||pHead.next ==null){
return 0;
}
ListNode slow = pHead;
ListNode fast = pHead;
//记录快慢指针第一次相遇之后为起点,直至第二次相遇所走过的步长
int step = 0;
while(slow !=null&&fast.next !=null){
slow = slow.next;
fast = fast.next.next;
if(fast.val == slow.val){
break;
}
}
if(fast==null||fast.next == null){
return step;
}
//此时,fast停留在首次碰撞的节点
while(fast.val != slow.next.val){
step++;
slow = slow.next;
}
return step;
}
- 求环的连接点在哪里
- 关于求连接点的问题,可以在第二问的基础上再进一步分析,因为在第二问求解过程中,记录了fast与slow首次相遇的节点P,可称之为碰撞点P(点P可能是在环的内部,也可能正好在连接点)。碰撞点P到环连接点的距离,就是头指针到连接点的距离。定理证明如下:
-
假设某含有环的链表如上图所示,X为头结点,Y为环的连接点,Z为快慢指针首次相遇的点。
则在快慢指针首次相遇时,
- slow走过的距离:L1 = a+b;
- fast 走过的距离:L2 = a+b+c+b。
又因为fast的速度是slow的两倍,所以fast走过的距离是slow走过的两倍,所以,L2 = 2*L1。即有2(a+b) = a+b+c+b。很容易得出,a = c,也就是说,头指针到连接点的距离,就是快慢指针首次相遇的碰撞点到连接点的距离。
所以在求连接点位置这个问题时,已经在第二问中求出了碰撞点P的位置(图中为Z),则可以让slow从头结点开始,fast从碰撞点Z开始,每次分别走一步,直至相遇,因为a=c,所以相遇时,一定在连接点Y处
/**
* 找出连接点
* @param pHead
* @return
*/
public ListNode getFirstJoin(ListNode pHead){
if(pHead == null ||pHead.next ==null){
return null;
}
ListNode slow = pHead;
ListNode fast = pHead;
//找出第一次碰撞的点
while(slow !=null&&fast.next !=null){
slow = slow.next;
fast = fast.next.next;
if(fast.val == slow.val){
break;
}
}
slow = pHead;
while(slow.val != fast.val){
slow = slow.next;
fast = fast.next;
}
return slow;
}
- 求带环链表长度
- 此问稍作分析即可得到带环链表长度L =a+ b+c
- 因为在第三问中,可以设置一个变量Len记录头结点到连接点的距离,换长度可以在第二问中求出。
/**
* 求带环链表长度
* @param pHead
* @return
*/
public int getLoopLength(ListNode pHead){
if(pHead == null ||pHead.next ==null){
return 0;
}
ListNode slow = pHead;
ListNode fast = pHead;
//头结点到连接点的距离
int LengthToJoin = 0;
//环长度
int LengthOfLoop = LengthOfLoopListNode(pHead);
//找出第一次碰撞的点
while(slow !=null&&fast.next !=null){
slow = slow.next;
fast = fast.next.next;
if(fast.val == slow.val){
break;
}
}
slow = pHead;
while(slow.val != fast.val){
LengthToJoin++;
slow = slow.next;
fast = fast.next;
}
return LengthOfLoop+LengthToJoin;
}