题目:
给定两个单链表的头节点 headA 和 headB (headA和headB可能有环也可能无环),请找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
环状链表。
分析:
- 首先判断两个单链表是否是环链表,如果是,则找出各自的环相交点处loopNode,如果不是,则return null。
- 根据各组相交点,判断两个两个链表相交情况
2.1 如果loopNode一个为null,一个不为null,则说明两个链表一定不想交
2.2 如果两个loopNode都为null,则遍历两个链表,取其长度,将长的链表遍历掉多余的长度,使两个链表等长,而后一同遍历并进行比较,如果相等,则说明两个链表的相交点就是该处,直接return。
2.3 如果 loopNode不为null,并且 loopNode1 == loopNode2,则说明两个链表环处相交点相同,则将loopNode作为tail,其余同2.2
2.4 如果loopNode不同,则说明在环上相交,则取其中任意一链表,遍历环处一圈,看在何处与另一节点相同。
代码实现:
public static class ListNode {
private int value;
private ListNode next;
public ListNode(int v) {
this.value = v;
}
public ListNode(int v, ListNode node) {
value = v;
next = node;
}
}
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null){
return null;
}
ListNode loopNode1 = getLoopNode(headA);
ListNode loopNode2 = getLoopNode(headB);
//loopNode 一个为null 另一个不为null ,则两个链表一定不会相交。
if (loopNode1 == null && loopNode2 == null){
return noLoop(headA,headB);
}
if(loopNode1 != null && loopNode2 != null){
return bothLoop(headA,loopNode1,headB,loopNode2);
}
return null;
}
//说明headA headB 都是环状链表,这种情况下,求第一次相交节点共有2中情况
//1. 节点相交处 在head ~ loopNode之前(头节点到环的相交节点之前,headA和headB就已经相交)
//2. 在环中相交。
public static ListNode bothLoop(ListNode headA, ListNode loopNode1, ListNode headB, ListNode loopNode2) {
ListNode cur1 = null;
ListNode cur2 = null;
//第一种情况:两个链表都是环链表,并且环的第一个相交点相同,那就是在head ~ loopNode之间两个链表第一次相交
if (loopNode1 == loopNode2 ){
cur1 = headA;
cur2 = headB;
int n = 0;
//将loopNode 作为tail节点,同 noLoop方法
//遍历到loopNode1,找到cur1和cur2的长度
while (cur1 != loopNode1){
n++;
cur1 = cur1.next;
}
while (cur2 != loopNode2){
n--;
cur2 = cur2.next;
}
//长的链表赋值给cur1,短的cur2
cur1 = n < 0 ? headB : headA;
cur2 = cur1 == headA ? headB : headA;
n = Math.abs(n);
//遍历多余的部分,使cur1和cur2一样长
while (n != 0){
n--;
cur1 = cur1.next;
}
//一起向下遍历,直到cur1 = cur2
while (cur1 != cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}else{
//如果cur1 != cur2说明两个链表在环处相交点不同
//将cur1绕着loopNode1遍历一圈,找到期间和cur2相交的点
cur1 = loopNode1.next;
while (cur1 != loopNode1){
if (cur1 == loopNode2){
//return loopNode1和loopNode2都行,看离哪个链表近一些
return loopNode1;
}
cur1 = cur1.next;
}
return null;
}
}
//判断单链表是否是环状链表,如果是 返回首次相交节点,如果不是,返回null
public static ListNode getLoopNode(ListNode head) {
if (head == null || head.next == null || head.next.next == null) {
return null;
}
ListNode fast = head.next.next;
ListNode slow = head.next;
while (fast != slow) {
//说明链表不是环状,直接return
if (fast.next == null || fast.next.next == null) {
return null;
}
fast = fast.next.next;
slow = slow.next;
}
//走到这,说明
//1.是环状链表
//2.fast 和 slow 相遇了一次
fast = head;
//第一次相遇后,fast节点从head处一步一步再次向下走,一定会和slow再次相遇。
//fast 和 slow 再次相遇 就是环状链表的首次相交节点
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return fast;
}
//说明loop1 和 loop2为null,两个单链表都不是环状链表
//那就要看两个链表是否相交(内存地址相同)
public static ListNode noLoop(ListNode headA,ListNode headB){
ListNode cur1 = headA;
ListNode cur2 = headB;
int n = 0;
//遍历cur1 和 cur2 获取到两个链表长度 以及tail节点
//循环条件是 next 不为null,这样就可以获取到tail节点
while (cur1.next != null){
n++;
cur1 = cur1.next;
}
while (cur2.next != null){
n--;
cur2 = cur2.next;
}
//如果遍历完后,两个链表的tail 节点不相同,说明肯定不想交,直接return null。
if (cur1 != cur2 ){
return null;
}
// 将长度长的链表 赋给cur1,短的赋给cur2
cur1 = n > 0 ? headA : headB;
cur2 = cur1 == headA ? headB : headA;
n = Math.abs(n);
//n取绝对值后, 让长的cur1链表先跑到和cur2的长度相等。
while (n != 0){
n--;
cur1 = cur1.next;
}
//两个链表长度相等开始遍历,同时向下跑,看什么时候相等,则就是第一次相交的节点。
while (cur1 != cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}