题目来自编程之美
题目:如何得到两个带环且相交的单链表的交点
分析:具体参考单链表面试题
情况1:相交的点,在环外
思路:使用指针追赶的方法。
1、求两个链表A和B的长度Length(A)和Length(B)
2、如果Length(A)> Length(B),则链表A指针先走 Length(A)- Length(B),链表B指针再开始走,则两个指针相遇的位置就是相交的第一个节点。
如果 Length(B)> Length(A),则链表B指针先走 Length(B)- Length(A),链表A指针再开始走,则两个指针相遇的位置就是相交的第一个节点。
情况2(图2)、相交的点,在环内,还不能处理
分析:当交点在环中时,此时的交点可以是A链表中的环入口点,也可以是B链表中环入口点。
为什么这么说?这是因为如果把B看出一个完整的链表,而A指向了B链表,则此时交点是A的环入口点。反之交点是链表B的环入口点。
思路:根据上述分析,可以直接求出A的环入口点或者B的环入口点就可以了。
综合这两种情况,给出两个方法求出结点:
方法一、先检测交点是否在第一种情况中。如果是,则直接输出该结点。如果不是,则直接输出任意一个环交点。
方法二、使用哈希,只需要把链表A或者B所有的元素全部放入哈希表中,之后把B链表或者A链表中的每一个元素去哈希探测,即可找到交点。
代码:
方法一的代码:
/*
需要处理两种情况:
1、是否相交于环外,使用指针追赶的方法
2、若不满足情况1,即不相交于环外,但两个链表又相交,则肯定相交于环内。
则此时直接返回任意一个环交点即可*/
ListNode* FindCrossingNode(ListNode* pFirstHead,ListNode* pSecHead)
{
//统计两个链表环外的结点个数
ListNode* pCurFirst = pFirstHead;
ListNode* pCurSec = pSecHead;
//统计链表长度
int nCountofFirst = GetNodeCountOfList(pFirstHead);
int nCountofSec = GetNodeCountOfList(pSecHead);
//获得链表入口结点
ListNode* pEntryOfFirst = FindCycleEntry(pFirstHead);
ListNode* pEntryOfSec = FindCycleEntry(pSecHead);
//初始化指针指向
while (nCountofFirst - nCountofSec > 0)
{
pCurFirst = pCurFirst->m_pNext;
nCountofFirst--;
}
while (nCountofSec - nCountofFirst > 0)
{
pCurSec = pCurSec->m_pNext;
nCountofSec--;
}
//两个指针往前走
while (pCurFirst != pEntryOfFirst && pCurSec != pEntryOfSec && pCurFirst != pCurSec)
{
pCurSec = pCurSec->m_pNext;
pCurFirst = pCurFirst->m_pNext;
}
if (pCurFirst != pCurSec) //交点在环中
{
return pEntryOfFirst;
}
else //交点在环内
{
return pCurFirst;
}
}
/*
思路:统计第一个结点到碰撞点之间结点个数 + 统计环中结点个数
1、先找到碰撞点
2、利用碰撞点,找到环入口点,同时统计第一个结点到碰撞点之间结点个数
3、利用碰撞点或者环入口点,在环中转圈,统计环中结点个数
*/
int GetNodeCountOfList(ListNode* pHead)
{
assert(pHead != NULL);
ListNode* pFast = pHead;
ListNode* pSlow = pHead;
ListNode* pEntry = pHead;
int nCountOfBeforeEntry = 0; //统计第一个结点到碰撞点之间结点个数
int nCountOfCycle = 1; //统计环中结点个数
while (pFast->m_pNext && pFast->m_pNext->m_pNext)
{
pFast = pFast->m_pNext->m_pNext;
pSlow = pSlow->m_pNext;
if (pFast == pSlow)//找到碰撞点
{
//查找环的入口结点,并统计第一个结点到碰撞点之间结点个数
while (pEntry != pSlow)
{
pEntry = pEntry->m_pNext;
pSlow = pSlow->m_pNext;
nCountOfBeforeEntry++;
}
//统计环中结点个数
pSlow = pFast;
pFast = pFast->m_pNext;
while (pFast != pSlow)
{
pFast = pFast->m_pNext;
nCountOfCycle++;
}
return nCountOfBeforeEntry + nCountOfCycle;
}
}
return 0;
}
/*在快慢指针相遇处,使用两个指针分别从头结点和碰撞处开始走,相遇点就是环入口点*/
ListNode* FindCycleEntry(ListNode* pHead)
{
assert(pHead != NULL);
ListNode* pFast = pHead;
ListNode* pSlow = pHead;
ListNode* pEntry = pHead;
while (pFast->m_pNext && pFast->m_pNext->m_pNext)
{
pFast = pFast->m_pNext->m_pNext;
pSlow = pSlow->m_pNext;
if (pFast == pSlow)//找到碰撞点
{
//查找环的入口结点
while (pEntry != pSlow)
{
pEntry = pEntry->m_pNext;
pSlow = pSlow->m_pNext;
}
return pEntry;
}
}
return NULL;
}
方法二的代码:
/*使用哈希,只需要把链表A或者B所有的元素全部放入哈希表中,之后把B链表或者A链表中的每一个元素去哈希探测,即可找到交点*/
ListNode* FindCrossingNode(ListNode* pFirstHead,ListNode* pSecHead)
{
//统计两个链表环外的结点个数
map<ListNode*,ListNode*> mapNode;
map<ListNode*,ListNode*>::iterator it;
ListNode* pCurFirst = pFirstHead;
ListNode* pCurSec = pSecHead;
//统计链表长度
int nCountofFirst = GetNodeCountOfList(pFirstHead);
int nCountofSec = GetNodeCountOfList(pSecHead);
//把一个链表的结点放入map
while (nCountofFirst > 0)
{
mapNode[pCurFirst] = pCurFirst;
pCurFirst = pCurFirst->m_pNext;
nCountofFirst--;
}
//把另一个链表的每一个结点去哈希探测,检测是否存在
while (pCurSec)
{
it = mapNode.find(pCurSec);
if (it != mapNode.end())
{
return it->second;
}
pCurSec = pCurSec->m_pNext;
}
return NULL;
}
/*
思路:统计第一个结点到碰撞点之间结点个数 + 统计环中结点个数
1、先找到碰撞点
2、利用碰撞点,找到环入口点,同时统计第一个结点到碰撞点之间结点个数
3、利用碰撞点或者环入口点,在环中转圈,统计环中结点个数
*/
int GetNodeCountOfList(ListNode* pHead)
{
assert(pHead != NULL);
ListNode* pFast = pHead;
ListNode* pSlow = pHead;
ListNode* pEntry = pHead;
int nCountOfBeforeEntry = 0; //统计第一个结点到碰撞点之间结点个数
int nCountOfCycle = 1; //统计环中结点个数
while (pFast->m_pNext && pFast->m_pNext->m_pNext)
{
pFast = pFast->m_pNext->m_pNext;
pSlow = pSlow->m_pNext;
if (pFast == pSlow)//找到碰撞点
{
//查找环的入口结点,并统计第一个结点到碰撞点之间结点个数
while (pEntry != pSlow)
{
pEntry = pEntry->m_pNext;
pSlow = pSlow->m_pNext;
nCountOfBeforeEntry++;
}
//统计环中结点个数
pSlow = pFast;
pFast = pFast->m_pNext;
while (pFast != pSlow)
{
pFast = pFast->m_pNext;
nCountOfCycle++;
}
return nCountOfBeforeEntry + nCountOfCycle;
}
}
return 0;
}
测试代码:
#include <iostream>
#include <assert.h>
using namespace std;
struct ListNode
{
int m_Data;
ListNode* m_pNext;
};
/*
思路:统计第一个结点到碰撞点之间结点个数 + 统计环中结点个数
1、先找到碰撞点
2、利用碰撞点,找到环入口点,同时统计第一个结点到碰撞点之间结点个数
3、利用碰撞点或者环入口点,在环中转圈,统计环中结点个数
*/
int GetNodeCountOfList(ListNode* pHead)
{
assert(pHead != NULL);
ListNode* pFast = pHead;
ListNode* pSlow = pHead;
ListNode* pEntry = pHead;
int nCountOfBeforeEntry = 0; //统计第一个结点到碰撞点之间结点个数
int nCountOfCycle = 1; //统计环中结点个数
while (pFast->m_pNext && pFast->m_pNext->m_pNext)
{
pFast = pFast->m_pNext->m_pNext;
pSlow = pSlow->m_pNext;
if (pFast == pSlow)//找到碰撞点
{
//查找环的入口结点,并统计第一个结点到碰撞点之间结点个数
while (pEntry != pSlow)
{
pEntry = pEntry->m_pNext;
pSlow = pSlow->m_pNext;
nCountOfBeforeEntry++;
}
//统计环中结点个数
pSlow = pFast;
pFast = pFast->m_pNext;
while (pFast != pSlow)
{
pFast = pFast->m_pNext;
nCountOfCycle++;
}
return nCountOfBeforeEntry + nCountOfCycle;
}
}
return 0;
}
/*在快慢指针相遇处,使用两个指针分别从头结点和碰撞处开始走,相遇点就是环入口点*/
ListNode* FindMeetNode(ListNode* pHead)
{
assert(pHead != NULL);
ListNode* pFast = pHead;
ListNode* pSlow = pHead;
ListNode* pMeetNode = pHead;
while (pFast->m_pNext && pFast->m_pNext->m_pNext)
{
pFast = pFast->m_pNext->m_pNext;
pSlow = pSlow->m_pNext;
if (pFast == pSlow)//找到碰撞点
{
return pFast;
}
}
return NULL;
}
/*思路:在A链表上,使用指针追赶的方法,找到两个指针碰撞点,之后判断碰撞点是否在B链表上。如果在,则相交 。*/
bool IsCrossing(ListNode* pFirstHead,ListNode* pSecHead)
{
assert(pFirstHead != NULL && pSecHead);
ListNode* pCurOfFirst = pFirstHead;
ListNode* pCurOfSec = pSecHead;
ListNode* pMeetNode = FindMeetNode(pCurOfFirst);//获得第一个链表的入口结点
int nCountOfSec = GetNodeCountOfList(pSecHead);//获得第二个带环链表中元素个数
//把第一个链表的入口点与第二个链表的每一个结点比较
for (int i = 1;i <= nCountOfSec;i++)
{
if (pMeetNode == pCurOfSec)
{
return true;
}
pCurOfSec = pCurOfSec->m_pNext;
}
return false;
}
/*在快慢指针相遇处,使用两个指针分别从头结点和碰撞处开始走,相遇点就是环入口点*/
ListNode* FindCycleEntry(ListNode* pHead)
{
assert(pHead != NULL);
ListNode* pFast = pHead;
ListNode* pSlow = pHead;
ListNode* pEntry = pHead;
while (pFast->m_pNext && pFast->m_pNext->m_pNext)
{
pFast = pFast->m_pNext->m_pNext;
pSlow = pSlow->m_pNext;
if (pFast == pSlow)//找到碰撞点
{
//查找环的入口结点
while (pEntry != pSlow)
{
pEntry = pEntry->m_pNext;
pSlow = pSlow->m_pNext;
}
return pEntry;
}
}
return NULL;
}
/*
需要处理两种情况:
1、是否相交于环外,使用指针追赶的方法
2、若不满足情况1,即不相交于环外,但两个链表又相交,则肯定相交于环内。
则此时直接返回任意一个环交点即可*/
ListNode* FindCrossingNode(ListNode* pFirstHead,ListNode* pSecHead)
{
//统计两个链表环外的结点个数
ListNode* pCurFirst = pFirstHead;
ListNode* pCurSec = pSecHead;
//统计链表长度
int nCountofFirst = GetNodeCountOfList(pFirstHead);
int nCountofSec = GetNodeCountOfList(pSecHead);
//获得链表入口结点
ListNode* pEntryOfFirst = FindCycleEntry(pFirstHead);
ListNode* pEntryOfSec = FindCycleEntry(pSecHead);
//初始化指针指向
while (nCountofFirst - nCountofSec > 0)
{
pCurFirst = pCurFirst->m_pNext;
nCountofFirst--;
}
while (nCountofSec - nCountofFirst > 0)
{
pCurSec = pCurSec->m_pNext;
nCountofSec--;
}
//两个指针往前走
while (pCurFirst != pEntryOfFirst && pCurSec != pEntryOfSec && pCurFirst != pCurSec)
{
pCurSec = pCurSec->m_pNext;
pCurFirst = pCurFirst->m_pNext;
}
if (pCurFirst != pCurSec) //交点在环中
{
return pEntryOfFirst;
}
else //交点在环内
{
return pCurFirst;
}
}
void CreateList(ListNode** pHead,int nLen)//头指针使用指针的指针
{
assert(*pHead == NULL && nLen > 0);
ListNode* pCur = NULL;
ListNode* pNewNode = NULL;
for (int i = 0;i < nLen;i++)
{
pNewNode = new ListNode;
cin>>pNewNode->m_Data;
pNewNode->m_pNext = NULL;
if (*pHead == NULL)
{
*pHead = pNewNode;
pCur = *pHead;
}
else
{
pCur->m_pNext = pNewNode;
pCur = pNewNode;
}
}
}
/*让第一个链表的尾指针指向第二个链表的第二个元素
如果第二个链表没有第二个元素,则两个链表不相交,
如果第二个链表有第二个元素,则两个链表交点在环外*/
void CreateCrossingList1(ListNode* pFirstHead,ListNode* pSecHead)
{
assert(pFirstHead != NULL && pSecHead);
ListNode* pCurOfFirst = pFirstHead;
ListNode* pCurOfSec = pSecHead;
//寻找第一个链表最后一个结点
while (pCurOfFirst->m_pNext)
{
pCurOfFirst = pCurOfFirst->m_pNext;
}
//第一个链表最后一个结点指向第二链表的第二个元素
if (pCurOfSec->m_pNext)
{
pCurOfFirst->m_pNext = pCurOfSec->m_pNext;
}
}
/*让第一个链表pFirstHead的尾指针指向第二个链表pSecHead的第四个元素
如果第二个链表没有第四个元素,则两个链表不相交,
如果第二个链表有第四个元素,则两个链表交点在环内*/
void CreateCrossingList2(ListNode* pFirstHead,ListNode* pSecHead)
{
assert(pFirstHead != NULL && pSecHead);
ListNode* pCurOfFirst = pFirstHead;
ListNode* pCurOfSec = pSecHead;
//寻找第一个链表最后一个结点
while (pCurOfFirst->m_pNext)
{
pCurOfFirst = pCurOfFirst->m_pNext;
}
//第一个链表最后一个结点指向第二链表的第四个元素
if (pCurOfSec->m_pNext && pCurOfSec->m_pNext->m_pNext && pCurOfSec->m_pNext->m_pNext->m_pNext)
{
pCurOfFirst->m_pNext = pCurOfSec->m_pNext->m_pNext->m_pNext;
}
}
/*判断是不是环*/
bool IsCycle(ListNode* pHead)
{
assert(pHead != NULL);
ListNode* pFast = pHead;
ListNode* pSlow = pHead;
while (pFast->m_pNext && pFast->m_pNext->m_pNext)
{
pFast = pFast->m_pNext->m_pNext;
pSlow = pSlow->m_pNext;
if (pFast == pSlow)
{
return true;
}
}
return false;
}
/*把链表最后一个结点指向第三个结点*/
void CreateCycle(ListNode* pHead)
{
assert(pHead);
ListNode* pLastNode = pHead;
while (pLastNode->m_pNext)
{
pLastNode = pLastNode->m_pNext;
}
if (pHead->m_pNext && pHead->m_pNext->m_pNext)
{
pLastNode->m_pNext = pHead->m_pNext->m_pNext;
}
}
int main()
{
int nLen = 0;
bool bIsCrossing = false;
//测试一,有交点且交点在环外
ListNode* pHeadFirst = NULL;
ListNode* pHeadSec = NULL;
ListNode* pCrossingNode = NULL;
//创建有环链表
cout<<"please input node num: ";
cin >> nLen;
CreateList(&pHeadFirst,nLen);
CreateCycle(pHeadFirst);
//创建有环链表
cout<<"please input node num: ";
cin >> nLen;
CreateList(&pHeadSec,nLen);
CreateCrossingList1(pHeadSec,pHeadFirst);//交点在环外
if (IsCycle(pHeadFirst) && IsCycle(pHeadSec))
{
cout<<"两个链表均有环!"<<endl;
bIsCrossing = IsCrossing(pHeadFirst,pHeadSec);
if (bIsCrossing)
{
cout<<"相交!"<<endl;
pCrossingNode = FindCrossingNode(pHeadFirst,pHeadSec);
cout<<"交点:"<<pCrossingNode->m_Data<<endl;
}
else
{
cout<<"不相交!"<<endl;
}
}
else
{
cout<<"两个链表不均有环!"<<endl;
}
//测试二,有交点且交点在环内
ListNode* pHeadFirst1 = NULL;
ListNode* pHeadSec1 = NULL;
//创建有环链表
cout<<"please input node num: ";
cin >> nLen;
CreateList(&pHeadFirst1,nLen);
CreateCycle(pHeadFirst1);
//创建有环链表
cout<<"please input node num: ";
cin >> nLen;
CreateList(&pHeadSec1,nLen);
CreateCrossingList2(pHeadSec1,pHeadFirst1); //交点在环内
if (IsCycle(pHeadFirst1) && IsCycle(pHeadSec1))
{
cout<<"两个链表均有环!"<<endl;
bIsCrossing = IsCrossing(pHeadFirst1,pHeadSec1);
if (bIsCrossing)
{
cout<<"相交!"<<endl;
pCrossingNode = FindCrossingNode(pHeadFirst1,pHeadSec1);
cout<<"交点:"<<pCrossingNode->m_Data<<endl;
}
else
{
cout<<"不相交!"<<endl;
}
}
else
{
cout<<"两个链表不均有环!"<<endl;
}
//测试三,均有环,但是无交点
ListNode* pHeadFirst2 = NULL;
ListNode* pHeadSec2 = NULL;
//创建有环链表
cout<<"please input node num: ";
cin >> nLen;
CreateList(&pHeadFirst2,nLen);
CreateCycle(pHeadFirst2);
//创建有环链表
cout<<"please input node num: ";
cin >> nLen;
CreateList(&pHeadSec2,nLen);
CreateCycle(pHeadSec2);
if (IsCycle(pHeadFirst2) && IsCycle(pHeadSec2))
{
cout<<"两个链表均有环!"<<endl;
bIsCrossing = IsCrossing(pHeadFirst2,pHeadSec2);
if (bIsCrossing)
{
cout<<"相交!"<<endl;
pCrossingNode = FindCrossingNode(pHeadFirst2,pHeadSec2);
cout<<"交点:"<<pCrossingNode->m_Data<<endl;
}
else
{
cout<<"不相交!"<<endl;
}
}
else
{
cout<<"两个链表不均有环!"<<endl;
}
system("pause");
return 1;
}