1.链表中环相关问题

1.1 链表中是否有环

有一个单向链表,链表中有可能出现“环”,就像下图这样。 那么,如何用程序来判断该链表是否为有环链表呢?

思路

创建两个指针p1和p2(在Java里就是两个对象引用),让它们同时指向这个链表的头节点。然后开始一个大循环,在循环体中,让指针p1每次向后移动1个节点,让指针p2每次向后移动2个节点,然后比较两个指针指向的节点是否相同。如果相同,则可以判断出链表有环,如果不同,则继续下一次循环。

类似于,小学奥数中的追及问题。此方法就类似于一个追及问题。 在一个环形跑道上,两个运动员从同一地点起跑,一个运动员速度快,另一个运动员速度慢。当两人跑了一段时间后,速度快的运动员必然会再次追上并超过速度慢的运动员,原因很简单,因为跑道是环形的。 假设链表的节点数量为n,则该算法的时间复杂度为O(n)。除两个指针外,没有使用任何额外的存储空间,所以空间复杂度是O(1)。

1.2 求环长

如果链表有环,如何求出环的长度?

思路

当两个指针首次相遇,证明链表有环的时候,让两个指针从相遇点继续循环前进,并统计前进的循环次数,直到两个指针第2次相遇。此时,统计出来的前进次数就是环长。 因为指针p1每次走1步,指针p2每次走2步,两者的速度差是1步。当两个指针再次相遇时,p2比p1多走了整整1圈。 因此,环长 = 每一次速度差 × 前进次数 = 前进次数。

1.3 求入环节点

思路

上图是对有环链表所做的一个抽象示意图。假设从链表头节点到入环点的距离是D,从入环点到两个指针首次相遇点的距离是S1,从首次相遇点回到入环点的距离 是S2。

那么,当两个指针首次相遇时,各自所走的距离是多少呢?

指针p1一次只走1步,所走的距离是D+S1。 指针p2一次走2步,多走了n(n>=1)整圈,所走的距离是D+S1+n(S1+S2)。 由于p2的速度是p1的2倍,所以所走距离也是p1的2倍,因此: 2(D+S1) = D+S1+n(S1+S2) 等式经过整理得出:D = (n-1)(S1+S2)+S2。

也就是说,从链表头结点到入环点的距离,等于从首次相遇点绕环n-1圈再回到 入环点的距离。

这样一来,只要把其中一个指针放回到头节点位置,另一个指针保持在首次相遇点,两个指针都是每次向前走1步。那么,它们最终相遇的节点,就是入环节点。

1.4 具体代码

/**

* 链表是否存在环问题

*/

public class LinkedListCycle {

/**

* 链表是否有环

* @param head 链表的头节点

*/

public static boolean hasCycle(Node head){

Node p1 = head;

Node p2 = head;

while(p2 != null && p2.getNext().getNext() != null){

p1 = p1.getNext();

p2 = p2.getNext().getNext();

if(p1 == p2)

return true;

}

return false;

}

/**

* 统计链表中环的长度

* @param head 链表的头节点

* @return -1-代表链表中不存在环

*/

public static int countCycleLength(Node head){

if(!hasCycle(head))

return -1;

Node p1 = head;

Node p2 = head;

int count = 0;//记录循环次数

int firstMeet = -1;//记录第一次相遇,循环的次数

int secondMeet = -1;//记录第二次相遇,循环的次数

while(true){

p1 = p1.getNext();

p2 = p2.getNext().getNext();

if(p1 == p2 && firstMeet != -1)//第二次相遇

secondMeet = count;

if(p1 == p2 && firstMeet == -1)//第一次相遇,为firstMeet赋值

firstMeet = count;

if(firstMeet != -1 && secondMeet != -1)//统计出第一次和第二次相遇的循环次数后,可以结束循环了

break;

count++;

}

return secondMeet - firstMeet;

}

/**

* 获取入环节点

* @param head 链表头节点

* @return null-代表链表中不存在环

*/

public static Node getEnterCycleNode(Node head){

if(!hasCycle(head))

return null;

Node p1 = head;

Node p2 = head;

Node firstMeetNode = null;

while(true){

p1 = p1.getNext();

p2 = p2.getNext().getNext();

if(p1 == p2){

firstMeetNode = p1;

break;

}

}

p1 = head;

p2 = firstMeetNode;

Node enterCycleNode = null;

while(true){

p1 = p1.getNext();

p2 = p2.getNext();

if(p1 == p2){

enterCycleNode = p2;

break;

}

}

return enterCycleNode;

}

private static class Node{

int data;

Node next;

Node(int data){

this.data = data;

}

public int getData() {

return data;

}

public Node getNext() {

return next;

}

}

public static void main(String[] args) {

Node node1 = new Node(1);

Node node2 = new Node(2);

Node node3 = new Node(3);

Node node4 = new Node(4);

Node node5 = new Node(5);

node1.next = node2;

node2.next = node3;

node3.next = node4;

node4.next = node5;

node5.next = node2;

System.out.println(hasCycle(node1));//true

System.out.println(countCycleLength(node1));//4

System.out.println(getEnterCycleNode(node1).getData());//2

}

}

只是为了记录自己的学习历程,且本人水平有限,不对之处,请指正。