这个问题是 LintCode 上面的一个算法题目( 带环链表 II ),主要考察的是对于链表,带环链表的理解。
原问题描述:
给定一个链表,如果链表中存在环,则返回到链表中环的起始节点,如果没有环,返回null。
样例:
给出 -21->10->4->5, tail connects to node index 1,返回10
分析:
这个问题的思路主要分为以下几步:
1.判断这个链表是不是带环链表
2.如果是带环链表,则返回环上的一个节点
3.从头节点开始,一步一步往后挪动,每次都判断节点是不是在环上面
判断方法: 使得环上的节点绕环一周,如果遇到了测试节点,则说明测试节点是环上面的一个节点
如果绕完一周以后,一直没有碰到测试节点,说明测试节点不是环上面的一个节点
解决代码:
public class TestDetectCycle {
public ListNode detectCycle(ListNode head){
//如果存在循环体,则链的下一个节点是不会为空的
if (head == null || head.next == null) return null;
//找到链上面的一个节点
ListNode nodeInCycle = findANodeInCycle(head, head.next);
//如果返回为空,则说明没有循环体
if (nodeInCycle == null) return null;
//将头结点和环上面任意节点传入,寻找起始点
return findStart(head, nodeInCycle);
}
/**
* 在链表上的循环体内找到一个节点
* @param node1 每次跨度为 1 的节点
* @param node2 每次跨度为2的节点
* @return 返回的是循环体内的一个节点,如果没有循环体,则返回null
*/
private ListNode findANodeInCycle(ListNode node1, ListNode node2) {
//只有任意一个节点为空,则说明没有循环体,返回null
if (node1 == null || node2 == null) return null;
//说明两个跨度不同的节点碰到了一起,那么此时此时节点就在循环体内
if (node1 == node2) return node1;
//这是为了防止下一行的代码报空指针异常
//只要某一个节点的下一个节点为空,则说明没有循环体
if (node2.next == null) return null;
//通过移动不同的跨度,寻找循环体
return findANodeInCycle(node1.next, node2.next.next);
}
/**
* 寻找循环体的起始节点
* @param node 头结点
* @param nodeInCycle 循环体上面的一个节点
* @return 返回其实节点
*/
private ListNode findStart(ListNode node, ListNode nodeInCycle) {
while ( ! isStartNode(node, nodeInCycle)){
//这个节点不是起始节点,则移动到下一节点,继续验证
node = node.next;
}
return node;
}
private boolean isStartNode(ListNode node, ListNode nodeInCycle) {
//为了让while循环体顺利进行,一开始不能让node2 == nodeInCycle,只要让node2往后挪动一步就行了
ListNode node2 = nodeInCycle.next;
//while代码段会让node2绕着循环体跑一圈,如果能遇到node,说明node就在循环体上
//如果绕完一圈后,还是没有遇到node,说明node不在循环体上面
while (node2 != nodeInCycle) {
if (node == node2) return true;
node2 = node2.next;
}
//因为当尾部节点指向其自身的话,while中的循环体是不会进去的,因为此时nodeInCycle == node2
//这时只要判断test节点node是不是等于nodeInCycle就行了
return node == node2;
}
}
关于判断链表是否有环,更具体的请参考我的另一篇博客
带环链表