漫画算法:小灰的算法之旅,还算是一本挺有意思的书,值得一读
有环链表
是一个很有意思的问题,这里讨论了三个问题:
判断一个链表有环
求环的长度
求环的入口点
,这个是最有意思,并且有点难度的问题
由于CSDN的编辑器对代码注释的排版的支持很low,我的字符画变乱了,并且移位了,这里我贴出完好的注释,下面的代码中如果有比较乱的注释,或者注释偏移了,可以参考如下的图片:
下面是完整的代码实现,代码的说明都在注释里写清楚了,很容易明白。
public class ListHasLoop {
/**
* 判断是否有环
* @param head 链表的头节点
* @return 如果有环,则返回值>0, 无环则返回0
*/
public static int hasLoop(Node head) {
Objects.requireNonNull(head);
int counter = 0;
Node p1 = head, p2 = head;
while(p2 != null && p2.next != null) {
p1 = p1.next;
p2 = p2.next.next;
counter++;
if(p1 == p2) {
return counter;
}
}
return 0;
}
/**
* 求环的入口节点
* S2
* - - - - - -
* / \
* / \
* / \
* \ / ←-- 首次相遇点
* \ / ↖
* - - - - - - - \ - - - - - -/ ↖
* ↖
* ↖
* ↑ ↑ ↖ ↖
* |----> D <----| ↖ ↖
* ← - - - - - - - - · S1
*
* 两个指针首次相遇时:
* p1所走距离为: D + S1
* p2所走距离为: n * (S1 + S2) + D + S1 (其中n>=1,为p2比p1多走的圈数)
*
* 由于p2的速度是p1的两倍,所以有如下的等式:
* 2 * (D + S1) = n * (S1 + S2) + D + S1
*
* 于是我们整理上面的等式:
* D + S1 = n * (S1 + S2)
* D = n * S1 - S1 + n * S2
* D = (n - 1) * (S1 + S2) + S2
*
* 求得D的值为:
* D = (n - 1) * (S1 + S2) + S2
*
* 现在我们需要分析一下这个D表达式的语义:
* 链表头节点到环入口的距离为:绕环转n-1圈后再走s2距离
*
* 那么我们现在可以这么求环的入口点:
* 将一个节点放置在首次相遇处,将一个节点放在头节点处,那么
* 这两个节点一步一步走,总会在环的入口点相遇
*
* 所以下面给出思路:
* *******************************************************************************
* * 求p1, p2首次相遇的节点,然后将p1置为头节点, *
* * p2置为相遇点,两者都一步一步走 *
* *******************************************************************************
* @param head
* @return
*/
public static Node loopEntryNode(Node head) {
Pair info = getFirstMeetInfoInLoop(head);
if(info.firstMeetNode == null || info.loopLength <= 0) {
return null;
}
Node p1 = head, p2 = info.firstMeetNode;
while(true) {
if(p1 == p2) {
return p1;
}
p1 = p1.next;
p2 = p2.next;
}
}
public static Pair getFirstMeetInfoInLoop(Node head) {
Objects.requireNonNull(head);
int counter = 0;
Node p1 = head, p2 = head;
while(p2 != null && p2.next != null) {
p1 = p1.next;
p2 = p2.next.next;
counter++;
if(p1 == p2) {
return new Pair(p1, counter);
}
}
return new Pair(null, 0);
}
/**
* 这里我们定义一个Pair
* 可以存放首次相遇的节点,以及环的长度
*
* 后续如果一次遍历需要获取比较多的信息,就可以用此数据结构
*/
private static class Pair {
Node firstMeetNode;
int loopLength;
public Pair(Node firstMeetNode, int loopLength) {
this.firstMeetNode = firstMeetNode;
this.loopLength = loopLength;
}
}
/**
* 1 ← 8
* ↓ ↑
* 5 -> 3 -> 7 -> 2 -> 6
*
* @param args
*/
public static void main(String[] args) {
Node node1 = new Node(5);
Node node2 = new Node(3);
Node node3 = new Node(7);
Node node4 = new Node(2);
Node node5 = new Node(6);
Node node6 = new Node(8);
Node node7 = new Node(1);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = node6;
node6.next = node7;
node7.next = node4;
// output:Node{data=2}
System.out.println(loopEntryNode(node1));
}
}