import java.util.HashSet;

/**
* 求两个链表第一个相交节点
*/
public class FindFirstIntersectNode {

public Node FindFirstIntersectNode(Node head1, Node head2) {
if (head1 == null || head2 == null) {
return null;
}
// 入环节点
Node loop1 = getLoopNode(head1);
Node loop2 = getLoopNode(head2);
// 都无环
if (loop1 == null && loop2 == null) {
return noLoops(head1, head2);
} else if (loop1 != null && loop2 != null) {
return bothLoops(head1, loop1, head2, loop2);
}
return null;
}

/**
* 都有环情况下的相交节点
*
* @param head1 链表1
* @param head2 链表2
*/
private Node bothLoops(Node head1, Node loop1, Node head2, Node loop2) {
Node cur1 = null;
Node cur2 = null;
// 如果同时入环,则相交节点在入环之前或者在入环的点,舍弃环,寻找交点,同都无环的寻找方法
if (loop1 == loop2) {
int n = 0;
cur1 = head1;
while (cur1 != loop1) {
n++;
cur1 = cur1.next;
}
cur2 = head2;
while (cur2 != loop2) {
n--;
cur2 = cur2.next;
}
cur1 = n >= 0 ? head1 : head2;
cur2 = cur1 == head1 ? head2 : head1;
// 链表尾部对齐
n = Math.abs(n);
while (n != 0) {
n--;
cur1 = cur1.next;
}
// 第一个相等的点即为相交节点,若不相交返回null(此处不可能)
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
// 如果入环节点不同,则节点都在环上,一个节点不动,一个节点往前走一圈,相遇即相交
cur1 = loop1.next;
while (cur1 != loop1) {
if (cur1 == loop2) {
return loop1;
}
cur1 = cur1.next;
}
return null;
}

/**
* 都无环情况下的相交节点
*
* @param head1 链表1
* @param head2 链表2
*/
private Node noLoops(Node head1, Node head2) {
// 链表长度差值,若相交,则共用链表尾部
int n = 0;
Node cur1 = head1;
while (cur1.next != null) {
n++;
cur1 = cur1.next;
}
Node cur2 = head2;
while (cur2.next != null) {
n--;
cur2 = cur2.next;
}
// 若尾部不一样,不可能相交
if (cur1 != cur2) {
return null;
}
cur1 = n >= 0 ? head1 : head2; // 谁长,谁的头变成cur1
cur2 = cur1 == head1 ? head2 : head1; // 谁短,谁的头变成cur2
// 链表尾部对齐
while (n != 0) {
n--;
cur1 = cur1.next;
}
// 第一个相等的点即为相交节点,若不相交返回null(此处不可能)
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}

/**
* 获取链表的入环节点,如无环,返回null
* HashSet实现
*
* @param head 链表头结点
* @return 入环节点
*/
public Node getLoopNode(Node head) {
if (head == null) {
return null;
}
HashSet<Node> nodeSet = new HashSet<>();
Node cur = head;
while (cur != null) {
if (nodeSet.contains(cur)) {
return cur;
}
nodeSet.add(cur);
cur = cur.next;
}
return null;
}

/**
* 快慢指针
* 快指针一次走两步,慢指针一次走一步,两个指针相遇之后,让快指针指向头结点并一次走一步,慢指针继续一次走一步,再次相遇的节点即为入环节点
* 此处已被数学证明是正确的,但是具体的证明过程我并不会
*
* @param head 链表头结点
* @return 入环节点
*/
public Node getLoopNode2(Node head) {
if (head == null || head.next == null || head.next.next == null) {
return null;
}
Node slow = head.next;
Node fast = head.next.next;
while (slow != fast) {
if (fast.next == null || fast.next.next == null) {
return null;
}
slow = slow.next;
fast = fast.next.next;
}
fast = head;
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return fast;
}

/**
* 链表结构
*/
public static class Node {

public int value;

public Node next;

public Node(int value) {
this.value = value;
}

}

}

/* 如有意见或建议,欢迎评论区留言;如发现代码有误,欢迎批评指正 */