【题目描述】
给定两个可能有环也可能无环的单链表,头节点head1和和head2。请实现一个函数,如果两个链表相交,请返回相交的第一个节点。如果不想交,返回null
【题目分析】
基于这两个链表分别可能有环和无环,基于单链表只能存在一个next指针的特性,我们分成3中情况讨论:
1)两个链表都无环
则两个链表想要相交,则一定是如下这种形态
即两个链表从某个节点开始,存在公共部分,公共部分的第一个节点则为相交节点。
2)其中一个链表有环,一个链表无环
由于单链表只能有一个next节点,有环链表的环一定在尾部,而结尾不同的两个链表是无法相交的。所以可以得出结论,不存在这样情况下的相交链表。
3)两个链表都有环
则两个链表想要相交,则可能是下面这种形态:相交部分在环外
或者下面这种形态:相交部分在环内,并且环的方向一定是统一的
【算法分析】
1、针对于上诉1)的情况:即两个无环节点判断相交
首先,如果这样的两个链表相交,则它们的最后一个节点一定是相同的。如果不同,则一定不想交;
其次,从图1可以看出:如果这样两个链表相交,从某一节点开始,两个剩余部分是相同的。则我们可以先遍历较长链表,等到剩余长度相同,两个链表同时遍历下去。如果两个链表相交,则一定存在某一节点是相同的;
代码如下
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
//判空
if(nullptr == headA || nullptr == headB)
{
return nullptr;
}
//1)两个无环链表
int lenN = 0; //用于记录两个链表的长度差值
//先遍历一个链表
ListNode* temp1 = headA, *temp2 = headB;
while (nullptr != temp1->next || nullptr != temp2->next)
{
//A链表遍历完成,tempA为A的最后一个节点,继续遍历B链表,得到B的最后一个节点tempB
if (nullptr == temp1->next)
{
while (nullptr != temp2->next)
{
temp2 = temp2->next;
--lenN;
}
break;
}
//B链表遍历完成,tempB为B的最后一个节点,继续遍历A链表,得到A的最后一个节点tempA
else if (nullptr == temp2->next)
{
while (nullptr != temp1->next)
{
temp1 = temp1->next;
++lenN;
}
break;
}
temp1 = temp1->next;
temp2 = temp2->next;
}
//tempA和tempB不相等,则不想交
if (temp1 != temp2)
{
return nullptr;
}
temp1 = lenN > 0 ? headA : headB; //将长链表头节点赋值temp1
temp2 = lenN > 0 ? headB : headA; //将短链表头节点赋值temp2
lenN = abs(lenN);
//长链表先行lenN个节点
while (lenN-- > 0)
{
temp1 = temp1->next;
}
//两个链表同时遍历,如果存在相等节点,则相交,否则不想交
while (nullptr != temp1)
{
if (temp1 == temp2)
{
return temp1;
}
temp1 = temp1->next;
temp2 = temp2->next;
}
return nullptr;
}
2、针对于3)图2的情况:两个链表相交于环外
则这两个链表的入环节点肯定是相同的,使用这一特点可以将3)图2和图3两种情况区分出来
这种情况,我们可以将两个链表的第一个入环节点作为"end"节点,将此作为两个无环链表相交问题求解
3)针对于3)图3的情况:两个链表相交于环内
两个链表的入环节点不同,分别记为loop1和loop2。则可见loop2是loop1所在环中的某一节点。则,我们可以让loop2“原地等待”,让loop1继续往下走,如果在转回loop1之前能够遇上loop2,则两个链表是相交的(loop1和loop2都可以作为第一相交节点返回),否则不想交。
下列代码实现有环链表的相交求解
ListNode *getLoopListIntersectionNode(ListNode *headA, ListNode *headB) {
//1)获取两个链表的第一个入环节点
ListNode* loop1 = hasCycle(headA), *loop2 = hasCycle(headB);
//2)如果两个入环节点相同,则一定相交,并看做两个无环节点相交问题求解相交节点
if (loop1 == loop2)
{
return getIntersectionNode(headA, headB, loop1);
}
//3)如果两个入环节点不同,则让loop2不动,loop1继续遍历
ListNode* temp = loop1->next;
while (temp != loop1)
{
if (temp == loop2)
{
return loop1;
}
temp = temp->next;
}
return nullptr;
}
完整实现请见下列【代码求解】部分
【代码求解】
getIntersectionNode函数为总入口
/*
说明 : 返回单链表的入环节点
参数 : head - 单链表的头节点
返回 : ListNode*入环节点指针,如果无环则返回nullptr
*/
ListNode* getLoopNode(ListNode* head)
{
ListNode* fast = head;
ListNode* slow = head;
//判断是否有环
while (nullptr != fast && nullptr != slow)
{
if (nullptr == fast->next) return nullptr;
fast = fast->next->next;
slow = slow->next;
//无环,返回空指针
if (nullptr == fast || nullptr == slow)
{
return nullptr;
}
if (fast == slow)
{
break;
}
}
//快指针回到头结点,快慢指针每次均走一步,相遇节点为第一个入环节点
fast = head;
while (nullptr != slow)
{
if (fast == slow)
{
break;
}
fast = fast->next;
slow = slow->next;
}
return slow;
}
/*
说明 : 无环链表获取相交节点求解
参数 : headA、headB - 单链表的头节点 comEnd 两个链表的共同尾结点,为两个入环节点相同的链表判断相交节点的场景设计
返回 : ListNode*入环节点指针,如果无环则返回nullptr
*/
ListNode *getNoLoopIntersectionNode(ListNode *headA, ListNode *headB, ListNode* comEnd = nullptr) {
//1)两个无环链表
int lenN = 0; //用于记录两个链表的长度差值
//先遍历一个链表
ListNode* temp1 = headA, *temp2 = headB;
while (comEnd != temp1->next || comEnd != temp2->next)
{
//A链表遍历完成,tempA为A的最后一个节点,继续遍历B链表,得到B的最后一个节点tempB
if (comEnd == temp1->next)
{
while (comEnd != temp2->next)
{
temp2 = temp2->next;
--lenN;
}
break;
}
//B链表遍历完成,tempB为B的最后一个节点,继续遍历A链表,得到A的最后一个节点tempA
else if (comEnd == temp2->next)
{
while (comEnd != temp1->next)
{
temp1 = temp1->next;
++lenN;
}
break;
}
temp1 = temp1->next;
temp2 = temp2->next;
}
//tempA和tempB不相等,则不想交
if (temp1 != temp2)
{
return comEnd;
}
temp1 = lenN > 0 ? headA : headB; //将长链表头节点赋值temp1
temp2 = lenN > 0 ? headB : headA; //将短链表头节点赋值temp2
lenN = abs(lenN);
//长链表先行lenN个节点
while (lenN-- > 0)
{
temp1 = temp1->next;
}
//两个链表同时遍历,如果存在相等节点,则相交,否则不想交
while (comEnd != temp1)
{
if (temp1 == temp2)
{
return temp1;
}
temp1 = temp1->next;
temp2 = temp2->next;
}
return comEnd;
}
/*
说明 : 链表相交节点求解
参数 : headA、headB - 单链表的头节点
返回 : ListNode*入环节点指针,如果无环则返回nullptr
*/
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB)
{
//判空
if (nullptr == headA || nullptr == headB)
{
return nullptr;
}
//1)获取两个链表的第一个入环节点
ListNode* loop1 = getLoopNode(headA), *loop2 = getLoopNode(headB);
//如果两个入环节点相同,则是2个无环链表或2个入环节点相同的有环链表场景
if (loop1 == loop2)
{
return getNoLoopIntersectionNode(headA, headB, loop1);
}
//两个有环链表,且入环节点不同
ListNode* temp = loop1->next;
while (temp != loop1)
{
if (temp == loop2)
{
return loop1;
}
temp = temp->next;
}
return nullptr;
}