本文是对http://blog.youkuaiyun.com/qq_35524916/article/details/66530326的补充
本文将要解决的面试题:
// 实现单链表的逆置:使用三个指针
PNode ReverseList(PNode pHead);
// 实现单链表的逆置:使用头插法
void ReverseList_P(PNode* pHead);
// 合并两个已序单链表,合并后依然有序
PNode MergeList(PNode pHead1, PNode pHead2);
// 判断两个单链表是否相交(链表不带环)
int IsListCross(PNode L1, PNode L2);
// 若不带环的单链表相交,求交点
PNode GetCrossNode(PNode PL1, PNode PL2);
// 判断链表是否带环,若带环给出相遇点
PNode HasCircle(PNode pHead);
// 求环的长度
size_t GetCircleLen(PNode pMeetNode);
// 求环的入口点
PNode GetEnterNode(PNode pHead, PNode pMeetNode);
1,实现单链表的逆置:使用三个指针
解析:利用pPre、pCur、pNext三个节点整体向后遍历,每遍历一次,改变三个的相对指向,即可。
注:链表为空,或只有一个节点。最后一个节点还需改变下一节点指向。
// 实现单链表的逆置:使用三个指针
PNode ReverseList(PNode pHead)
{
PNode pPreNode = NULL;//要移动节点的前一节点
PNode pCurNode = NULL;//要移动的节点
PNode pNextNode = NULL;//要移动节点的后一节点
//当链表为空或只有一个节点时,不需逆置,直接返回
if ((NULL == pHead) || (NULL == pHead->_pNext))
return pHead;
pPreNode = pHead;
pCurNode = pPreNode->_pNext;
pNextNode = pCurNode->_pNext;
//遍历到链表尾
while (NULL != pCurNode->_pNext)
{ //改变pCurNode->_pNext指向
pCurNode->_pNext = pPreNode;
pPreNode = pCurNode;
pCurNode = pNextNode;
pNextNode = pNextNode->_pNext;
}
//剩余最后一个节点改变指向
pCurNode->_pNext = pPreNode;
//原链表的头结点指向空
pHead->_pNext = NULL;
//头指针指向原链表的尾节点
pHead = pCurNode;
return pHead;
}
2,实现单链表的逆置:使用头插法
解析:思想比较简单,一个指针每遍历一个节点,就将当前节点头插在头指针前面。
注:指针为空。
// 实现单链表的逆置:使用头插法
void ReverseList_P(PNode *pHead)
{
PNode pPreNode = NULL;
assert(NULL != pHead);
//当链表为空时,返回NULL
if (NULL == *pHead)
return ;
pPreNode = *pHead;
while (NULL != pPreNode->_pNext)
{
//当前要移到开头的节点
PNode pCurNode = pPreNode->_pNext;
//跳过pCurNode节点
pPreNode->_pNext = pCurNode->_pNext;
//移到开头
pCurNode->_pNext = *pHead;
//重置头结点
*pHead = pCurNode;
}
}
3,合并两个已序(从小到大)单链表,合并后依然有序
解析:首先,定义一个新的头结点pNewNode,然后指向两个链表中头结点小的节点。比较两个链表PL1,PL2当前节点的大小。
如果PL1->_data > PL2->_data,则pNewNode的下一节点指针指向PL2,PL2往后移一步。一次类推。直到有一个链表遍历完成,pNewNode直接指向另一链表。
注:链表为空。
在pNewNode的下一节点指针指向某一链表中的节点时,要先保存该节点的地址,然后链表往后移一步,pNewNode下一节点在指向保存的节点。避免陷入死循环。
// 合并两个已序(从小到大)单链表,合并后依然有序
PNode MergeList(PNode pHead1, PNode pHead2)
{
PNode PL1 = pHead1;
PNode PL2 = pHead2;
PNode pNewHead = NULL;//合并后链表的头结点
PNode pNewNode = NULL;//加入的新节点
if (NULL == pHead1)
return pHead2;
else if (NULL == pHead2)
return pHead1;
//设置新的头结点指向。
if (PL1->_data < PL2->_data)
{
pNewHead = pNewNode = PL1;
PL1 = PL1->_pNext;
}
else
{
pNewHead = pNewNode = PL2;
PL2 = PL2->_pNext;
}
//当PL1和PL2都未遍历完
while (PL1 && PL2)
{
if (PL1->_data < PL2->_data)
{ //关键点:设置一个临时节点,PL1先自加,然后pNewNode->_pNext指向tmep,添加新节点。
//不应该先指向后加,这样会陷入死循环
PNode temp = PL1;
PL1 = PL1->_pNext;
pNewNode->_pNext = temp;
}
else
{
PNode temp = PL2;
PL2 = PL2->_pNext;
pNewNode->_pNext = temp;
}
pNewNode = pNewNode->_pNext;
}
//如果还有链表没有遍历完,则将pNewNode->_pNext指向该链表,
//添加该链表的剩余部分。
if (PL1)
pNewNode->_pNext = PL1;
else
pNewNode->_pNext = PL2;
//返回新链表的头指针
return pNewHead;
}
4,判断两个单链表是否相交(链表不带环)
解析:若是两个不带环单链表相交,那么他们的尾指针一定相同。只需判断他们的尾指针是否相同即可。
注:判断两个单链表是否相交不能空指针。
// 判断两个单链表是否相交(链表不带环)
int IsListCross(PNode PL1, PNode PL2)
{ //采用判断尾部是否相等来判断是否相交
PNode pTailNode_1 = NULL;//L1尾节点
PNode pTailNode_2 = NULL;//L2尾节点
assert(NULL != PL1);
assert(NULL != PL2);
pTailNode_1 = Back(PL1);
pTailNode_2 = Back(PL2);
return (pTailNode_1 == pTailNode_2);
}
5,若不带环的单链表相交,求交点
解析:先获取两个链表长度只差,然后让长链表先走,补上这个差值。
然后,两个链表同时遍历,直到遍历完毕,或者两个链表当前节点相同,即有交点。
注:两个链表为空。
// 若不带环的单链表相交,求交点
PNode GetCrossNode(PNode PL1, PNode PL2)
{ //根据两个链表不同部分长度之差,先让较长链表走它两只差的步长
//然后,两个链表同时开始遍历,直到两者所指向节点相同,即找到交点。
//获得PL1和PL2长度
size_t len_1 = 0;
size_t len_2 = 0;
//如果存在空链表,返回NULL
if ((NULL == PL1) || (NULL == PL2))
return NULL;
//如果PL1和PL2不相交,返回NULL
if (!IsListCross(PL1, PL2))
return NULL;
len_1 = Size(PL1);
len_2 = Size(PL2);
//PL1长度大于PL2
if (len_1 > len_2)
{ //长度之差
size_t diff = len_1 - len_2;
//PL1先走diff步
while (diff--)
PL1 = PL1->_pNext;
}
else //PL2长度大于PL1
{ //长度之差
size_t diff = len_2 - len_1;
//PL2先走diff步
while (diff--)
PL2 = PL2->_pNext;
}
//当PL1和PL2指向的节点不同时
while (PL1 != PL2)
{
PL1 = PL1->_pNext;
PL2 = PL2->_pNext;
}
//此时PL1和PL2指向节点相同,即链表的交点
return PL1;
}
6,判断链表是否带环,若带环,给出相遇点(不是环的入口点)
解析:定义一个快慢指针,快指针每次走一步,慢指针每次走一步。
当快指针不为尾节点时,一直遍历链表,直到快慢指针相同。即说明有环,返回相遇点pMeetNode。
注:链表为空或只有一个节点时,返回NULL。
// 判断链表是否带环,若带环给出相遇点(不是环的入口)
PNode HasCircle(PNode pHead)
{ //定义一个快指针,每次走两步
//定义一个慢指针,每次走一步
//当两个相交时,即证明链表有环
PNode pFast = NULL;
PNode pSlow = NULL;
//快慢指针相交的节点
PNode pMeetNode = NULL;
//当链表为空或只有一个节点时,链表没有环,返回NULL
if ((NULL == pHead) || (NULL == pHead->_pNext))
return NULL;
pFast = pHead;
pSlow = pHead;
//以pFast遍历为条件
//需要同时判断pFast和pFast->_pNext是否为空,因为pFast一次走两步
while ((pFast != NULL) && (NULL != pFast->_pNext))
{
pFast = pFast->_pNext->_pNext;
pSlow = pSlow->_pNext;
//有环
if (pFast == pSlow)
{
pMeetNode = pFast;
return pMeetNode;
}
}
//链表不带环
return NULL;
}
7,求环的长度
解析:利用一指针pCurNode指向相遇点,遍历环,直到pCurNode再次等于pMeetNode,计算遍历次数返回即可。
注:环的初始化为1.
相遇点为空。
// 求环的长度
size_t GetCircleLen(PNode pMeetNode)
{//思路:遍历环,知道pCurNode==pMeetNode,即可。
PNode pCurNode = NULL;//需要遍历的当前节点
size_t length = 1;//环的长度初始化为1
if (NULL == pMeetNode)
return 0;
pCurNode = pMeetNode;
while (pCurNode->_pNext != pMeetNode)
{
++length;
pCurNode = pCurNode->_pNext;
}
return length;
}
8,求环的入口点
解析:在一直带环链表的相遇点后,可以以此为基础,将该链表分为两个单链表。变成求两个单链表交点的问题
其中一个链表的头结点是pHead,尾节点是pMeetNode。
另一个链表的头结点是pMeetNode的下一节点,尾节点是pMeetNode。
注:链表为空或链表长度为1.
// 求环的入口点
PNode GetEnterNode(PNode pHead, PNode pMeetNode)
{
PNode PL1 = NULL;
PNode PL2 = NULL;
size_t len_1 = 1;
size_t len_2 = 1;
if ((NULL == pHead) || (NULL == pHead->_pNext))
return NULL;
if (NULL == HasCircle(pHead))
return NULL;
//可以看做是两个不带环单链表相交,求交点
//一个链表的头结点是pHead,尾节点是pMeetNode
//一个链表的头结点是pMeetNode->_pNext,尾节点是pMeetNode
PL1 = pHead;
PL2 = pMeetNode->_pNext;
//接下来采用求得两个不带环单链表求交的方法。
while (PL1 != pMeetNode)
{//计算len_1
++len_1;
PL1 = PL1->_pNext;
}
while (PL2 != pMeetNode)
{//计算len_2
++len_2;
PL2 = PL2->_pNext;
}
//两个链表头指针重新指向对应的头结点
PL1 = pHead;
PL2 = pMeetNode->_pNext;
if (len_1 > len_2)
{ //PL1先走diff步
size_t len_diff = len_1 - len_2;
while (len_diff--)
PL1 = PL1->_pNext;
}
else
{ //PL1先走diff步
size_t len_diff = len_2 - len_1;
while (len_diff--)
PL2 = PL2->_pNext;
}
//现在PL1和PL2距离环的入口点距离相同
//如果PL1和PL2不同,一直走下去
while (PL1 != PL2)
{
PL1 = PL1->_pNext;
PL2 = PL2->_pNext;
}
//返回交点
return PL1;
}