一:查找单链表的中间节点,要求只能遍历一次链表
思路:因为只能遍历一次链表,所以常规方法肯定不行,假设有两个人从相同起点跑步,跑的快的是跑的慢的人的两倍,当跑的快的的人跑到终点时,跑的慢的的人恰好处于中间位置,这个题用这种方法解决,代码实现如下:
PNode FindMiddleNode(PNode pHead)
{
PNode pFast = pHead;
PNode pSlow = pHead;
PNode pPre = pHead;
//第一个条件为了保证可以走第二步,
//第二个条件为了保证可以走出第一步
while ((pFast) && (pFast->_pNext))//前面的条件一定要放pFast
{
pPre = pSlow;
pFast = pFast->_pNext->_pNext;
pSlow = pSlow->_pNext;
}
if (pFast == NULL)//偶数出来时pFast为空,奇数pFast->_Next为空
return pPre;//返回较小的中间节点
return pSlow;//若链表长度为偶数,返回的是较大的中间节点
}
二:查找单链表的倒数第k个节点,要求只能遍历一次链表
思路:与上题思路一致,可以创建两个指针,一个指针先走k步,再让俩个指针一起走,当快指针走到NULL时,慢指针就停在倒数第k个。
PNode FindLastKNode(PNode pHead, int K)
{
PNode pFast = pHead;
PNode pSlow = pHead;
if (NULL == pHead || (K <= 0))
{
printf("K不合法!!!\n");
return NULL;
}
//让pFast先走K步
while (K--)
{
//K大于链表中节点的个数
if (NULL == pFast)
return NULL;
pFast = pFast->_pNext;
}
while (pFast)
{
pFast = pFast->_pNext;
pSlow = pSlow->_pNext;
}
return pSlow;
}
三:删除链表的倒数第K个结点
void DeleteLastKNode(PNode *pHead, int K)
{
assert(pHead);
PNode pFast = *pHead;
PNode pSlow = *pHead;
PNode pPre = *pHead;
if (NULL == pHead || (K <= 0))
{
printf("K不合法!!!\n");
return;
}
//让pFast先走K步
while (K--)
{
//K大于链表中节点的个数
if (NULL == pFast)
return;
pFast = pFast->_pNext;
}
while (pFast)
{
pFast = pFast->_pNext;
pPre = pSlow;//pSlow在走之前,先保存,方便以后删除
pSlow = pSlow->_pNext;
}
//是第一个节点
if (pSlow == pPre)
{
*pHead = pSlow->_pNext;
free(pSlow);
return;
}
//不是第一个节点
pPre->_pNext = pSlow->_pNext;
free(pSlow);
}
四:判断两个链表是否相交,若相交,求交点。(假设链表不带环)
如果两个链表相交,只有下面两种情况:
从上图我们可以知道,如果两个链表相交,那么他们最后一个节点肯定相同,判断是否相交,代码实现如下:
int IsSListCross(PNode pHead1, PNode pHead2)
{
//1.如果俩链表有一个为空那么肯定不相交
if (NULL == pHead1)
return 0;
if (NULL == pHead2)
return 0;
//2.如果两链表相交,那么它们最后一个节点肯定相同
while (pHead1->_pNext)
{
pHead1 = pHead1->_pNext;
}
while (pHead2->_pNext)
{
pHead2 = pHead2->_pNext;
}
if (pHead1 == pHead2)
return 1;
else
return 0;
}
如何求交点?如果链表一的头节点到交点的距离L1与链表二的头节点到交点的距离L2相等,那么我们很容易就可以求出交点,但是,如果不想等呢?所以,我们必须先求出两个链表长度的差值,然后创建两个分别指向两个头节点的指针,让指向较长链表的指针,先走俩链表差的步数,然后两指针再一起走,直到相等,此时,就找到交点,代码实现如下:
PNode GetCorssNode(PNode pHead1, PNode pHead2)
{
PNode pCur1 = NULL;
PNode pCur2 = NULL;
int size1 = 0;
int size2 = 0;
int gap = 0;
//1.先判断两个链表是否相交
if (!IsSListCross(pHead1, pHead2))
return NULL;
pCur1 = pHead1;
pCur2 = pHead2;
//2.求两链表长度的差值gap
while (pCur1->_pNext)
{
size1++;
pCur1 = pCur1->_pNext;
}
while (pCur2->_pNext)
{
size2++;
pCur2 = pCur2->_pNext;
}
gap = size1 - size2;
//3.让较长的链表从头部先走gap步
pCur1 = pHead1;
pCur2 = pHead2;
if (gap > 0)//说明链表1较长
{
while (gap--)
{
pCur1 = pCur1->_pNext;
}
}
else
{
while (gap++)
{
pCur2 = pCur2->_pNext;
}
}
//4.两指针一起走,直到相等或为空
while (pCur1 && pCur2 && (pCur1 != pCur2))
{
pCur1 = pCur1->_pNext;
pCur2 = pCur2->_pNext;
}
if ((pCur1 == NULL) || (pCur2 == NULL))
return NULL;
else
return pCur1;
}
五:判断单链表是否带环?若带环,求环的长度?求环的入口点?
思路:如果链表带环,我们可以创建两个指向头节点的指针,一个快,一个慢(注:如果俩差值刚好是环的长度,则不行),那么他们最终一定相遇,返回相遇点,代码实现如下:
PNode IsListWithCircle(PNode pHead1)
{
PNode pFast = pHead1;
PNode pSlow = pHead1;
if (NULL == pHead1)
return NULL;
while (pFast && pFast->_pNext)
{
pFast = pFast->_pNext->_pNext;
pSlow = pSlow->_pNext;
if (pFast == pSlow)//如果带环,肯定相遇
return pSlow;
}
return NULL;
}
求环长度,我们可以从上面的代码得到相遇点,创建一个指针指向相遇点,让其朝后走,如果最后回到相遇点,则此指针恰好走了一周,代码实现如下:
int GetCircleLen(PNode pHead1, PNode pMeetNode)
{
PNode ptr = NULL;
int len = 0;
pMeetNode = IsListWithCircle(pHead1);
if (!pMeetNode)
return 0;
ptr = pMeetNode;
while (ptr->_pNext != pMeetNode)//因为是下个节点,所以求出的长度少一
{
len++;
ptr = ptr->_pNext;
}
return len + 1;
}
从上题中我们可以得到以下结论:
求环入口节点思路:假设链表带环,那么创建两个指针,一个从链表头部开始走,一个从相遇点开始走,那么他们的相遇点必定是环的入口,代码实现如下:
PNode GetCircleEnter(PNode pHead1, PNode pMeetNode)
{
PNode pCur1 = NULL;
PNode pCur2 = NULL;
if (NULL == pMeetNode || NULL == pMeetNode)//若不带环返回
return NULL;
pCur1 = pHead1;
pCur2 = pMeetNode;
while (pCur1 != pCur2)
{
pCur1 = pCur1->_pNext;
pCur2 = pCur2->_pNext;
}
return pCur1;
}
六:判断两个链表是否相交,若相交,求交点。(假设链表可能带环)【升级版】
思路:分为四种情况,1.两个都不带2.两个都带3.两个有一个带(肯定不相交,不用讨论),如图:
如何判断是否相交,若是情况1,上面已解决,情况2,先判断是否带环,得到两个相遇点,创建一个指针,让其指向随意一个相遇点,走一周,若与另一个相遇点相等,那么就证明相交,否则,不想交,代码实现如下;
int IsSListCrossWithCircle(PNode pHead1, PNode pHead2)
{
PNode pMeetNode1 = NULL;
PNode pMeetNode2 = NULL;
if (NULL == pHead1)
return 0;
if (NULL == pHead2)
return 0;
//1.判断两个链表是否带环
pMeetNode1 = IsListWithCircle(pHead1);
pMeetNode2 = IsListWithCircle(pHead2);
if (NULL == pMeetNode1 && NULL == pMeetNode2)//两链表都不带环
{
PNode pTail1 = pHead1;
PNode pTail2 = pHead2;
while (pTail1->_pNext)
pTail1 = pTail1->_pNext;
while (pTail2->_pNext)
pTail2 = pTail2->_pNext;
if (pTail1 == pTail2)
return 1;
}
else if (pMeetNode1 && pMeetNode2)//两个都带环
{
//1.环内相交
//2.环外相交(都用下面的方式处理)
//一个指针指向一个相遇点,如果指针
//绕环一周没有和另一个相遇,那么就不相交
PNode pCur = pMeetNode1;
while (pCur->_pNext != pMeetNode1)
{
if (pCur == pMeetNode2)
return 2;
pCur = pCur->_pNext;
}
//如果第二个相遇点恰好在最后一个节点,上面处理不了
if (pCur == pMeetNode2)
return 2;
}
return 0;
}
如何求交点?情况一已解决,情况二中的第一种:
如实第二种;则就是分别求两个带环链表的入口点
七:复杂链表的复制
复杂链表是什么?一个链表的每个节点,有一个指向next指针指向下一个节点,还有一个random指针指向这个链表中的一个随机节点或者Null,现在要求实现复制这个链表,返回复制后的新链表。
方法:1.在链表后面插入值相同的新节点2.给新节点的随机指针域赋值让随机指针域指向原节点随机指针指向的下个节点3.将新节点从原链表中拆下来。代码实现如下:
PCSLNode CopyComplexList(PCSLNode pHead)
{
PCSLNode pCur = pHead;
PCSLNode pNewNode = NULL;
PCSLNode pNewHead = NULL;
if (NULL == pHead)
return NULL;
//1.在链表后面插入值相同的新节点
while (pCur)
{
pNewNode = BuyCSNewNode(pCur->data);
pNewNode->_pNext = pCur->_pNext;
pCur->_pNext = pNewNode;
pCur = pNewNode->_pNext;
}
pCur = pHead;
//2.给新节点的随机指针域赋值
//让随机指针域指向原节点随机指针指向的下个节点
while (pCur)
{
pNewNode = pCur->_pNext;
if (pCur->_pRandom)
{
pNewNode->_pRandom = pCur->_pRandom->_pNext;
}
pCur = pNewNode->_pNext;
}
//3.将新节点从原链表中拆下来
pCur = pHead;
pNewHead = pHead->_pNext;
while (pCur->_pNext)
{
pNewNode = pCur->_pNext;
pCur->_pNext = pNewNode->_pNext;
pCur = pNewNode;
}
return pNewHead;
}
方法二:可以创建一个新链表,新链表中的data和指针_pNext与原链表一致,指针pRandom都指向空,然后分别处理原链表的每个节点中的pRrandom,处理第一个节点,可以创建一个指针指向头节点,然后看指针走几步可以和该节点中的pRandom相等,然后让新链表中的该节点的pRandom指针也走同样的步数。‘但这种方法时间复杂度太大,为O(n^2)。