关于单链表的一些面试题-进阶篇(C语言实现)
1.判断单链表是否带环?若带环,求环的长度?求环的入口点?
上述问题可以分解成:
(1)判断单链表是否带环
(2)求带环单链表的环的长度
(3)求带环单链表的环的入口点
下面对上述问题分别进行求解:
* 判断单链表是否带环
易知带环单链表的特性,所以我们在这里使用快慢指针的方法来判断单链表是否带环。
若带环,则快慢指针最终会在环内相遇。
而若不带环,则快慢指针始终无法相遇。
以此为原理,我们即可实现判断单链表是否带环的函数。
示意图:
具体代码实现:
ListNode* IsCycle(ListNode* pList)//判断链表是否带环
{
ListNode* slow = pList;
ListNode* fast = pList;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
return slow;
}
return NULL;
}
思考:在代码实现中,我们给快指针的定义是fast->next->next,即快指针一次走两步。那么在此处,快指针为何不能一次走三步或者是走四步呢?
* 求带环单链表的环的长度
只要单链表带环,则快慢指针必定会在环内相遇。
我们可以通过IsCycle函数来求出这个相遇点,然后围绕着这个相遇点走一圈,并通过一个临时变量记录下这个次数,即可求出返回的长度
示意图:
具体代码实现:
int GetCycleLen(ListNode* pList)//求带环链表环的长度
{
ListNode* meetNode = IsCycle(pList);
ListNode* cur = meetNode->next;
int count = 1;
if(meetNode == NULL)
{
return 0;
}
else
{
while(meetNode != cur)
{
cur = cur->next;
count++;
}
return count;
}
}
* 求带环单链表的环的入口点
如图所示,设头结点至入口点的长度为L,入口点至相遇点的长度为X,环长为C
易知,在快慢指针相遇时,
快指针已走过的步数为:L+N*C+X(相遇之前快指针可能已经绕环多次)
慢指针已走过的步数为:L+X
又慢指针每走一步,快指针移动两步,即快指针移动的步数是慢指针的两倍
故:2(L+X) = L + N*C+X
2L+2X = L + N*C+X
L = N*C-X
因此,从相遇点到入口点的距离 = 头结点至入口点的距离(即 绿色线区域 = L)
由以上推理出的原理,有具体代码实现如下:
ListNode* GetCycleEntry(ListNode* pList)//求带环链表的环的入口点
{
ListNode* start = pList;
ListNode* ret = IsCycle(pList);
if(start == NULL)
return NULL;
else
{
while(start != ret)
{
start = start->next;
ret = ret->next;
}
return ret;
}
}
2.判断两个链表是否相交,若相交,求交点。(假设链表不带环)
链表相交(不带环)有两种情况,如下图所示:
采取gap结点计算长短链表的方法,可以很好的求出两链表是否带环,且返回交点或者空指针。
具体实现代码如下所示:
ListNode* CheckIsMeet(ListNode* pList1, ListNode* pList2)//求两链表是否相交(链表不带环)
{
ListNode* cur1 = pList1;
ListNode* cur2 = pList2;
ListNode* LongList = pList1;
ListNode* ShortList = pList2;
int gap = 0;
int len1 = 0;
int len2 = 0;
while(cur1)
{
len1++;
cur1 = cur1->next;
}
while(cur2)
{
len2++;
cur2 = cur2->next;
}
if(len1<len2)
{
LongList = pList2;
ShortList = pList1;
}
gap = abs(len1-len2);
while(gap--)
{
LongList = LongList->next;
}
while(LongList)
{
if(LongList == ShortList)
return ShortList;
LongList = LongList->next;
ShortList =ShortList->next;
}
return NULL;
}
3.判断两个链表是否相交,若相交,求交点。(假设链表可能带环)【升级版】
假设链表带环,则相交有以下几种情况,如图所示:
具体代码实现如下:
ListNode* CheckIsCross(ListNode* pList1, ListNode* pList2)//求两链表是否相交(链表可能带环)
{
ListNode* enter1 = GetCycleEntry(pList1);
ListNode* enter2 = GetCycleEntry(pList2);
ListNode* cur = enter1;
//两链表都不带环
if(IsCycle(pList1) == NULL && IsCycle(pList2) == NULL)
{
CheckIsMeet(pList1,pList2);
}
//两链表中有一个带环,一个不带环-——即两链表不可能相交
else if(IsCycle(pList1) == NULL || IsCycle(pList2) == NULL)
{
return NULL;
}
else
{
//交点在环外
//去环,转化为链表不带环的相交问题
if(enter1 == enter2)
{
enter1->next = NULL;
CheckIsMeet(pList1,pList2);
}
//交点在环内
//遍历环,若找到另一个入口点,则返回其中一个入口点
else
{
cur = enter1->next;
if(cur != enter1)
{
if(cur == enter2)
return enter2;
cur = cur->next;
}
return NULL;
}
}
}
4.复杂链表的复制。一个链表的每个节点,有一个指向next指针指向下一个节点,还有一个random指针指向这个链表中的一个随机节点或者NULL,现在要求实现复制这个链表,返回复制后的新链表。
- 复杂链表的复制具体思路如下:
1、在原链表的每个节点后面插入一个新结点,新结点的data与之前的结点data相同
2、将原结点的random赋给新结点的random
3、取出新结点,链成新链表
- 代码实现如下所示
ComplexNode* BuyNode(DataType x)//复杂链表的创建
{
ComplexNode* ptr = (ComplexNode*)malloc(sizeof(ComplexNode));
ptr->_data = x;
ptr->_next = NULL;
ptr->_random = NULL;
return ptr;
}
void PrintComplexList(ComplexNode* cList)//打印复杂链表
{
ComplexNode* cur = cList;
while(cur)
{
printf("%d->",cur->_data);
cur = cur->_next;
}
printf("NULL\n");
}
ComplexNode* CopyNodeList(ComplexNode* cList)//复杂链表的复制
{
ComplexNode* cur = cList;
ComplexNode* tmp = NULL;
ComplexNode* newList = NULL;
//链表中没有元素
if(cList == NULL)
return NULL;
//链表中只有一个元素
else if(cList->_next == NULL)
{
return BuyNode(cList->_data);
}
//链表中有多个元素
else
{
//复制相同的元素到每个结点之后去
while(cur)
{
tmp = BuyNode(cur->_data);
tmp->_next = cur->_next;
cur->_next = tmp;
cur = cur->_next->_next;
}
while(cur)
{
//讨论random是否为空
if(cur->_random == NULL)
cur->_next->_random = NULL;
else
{
cur->_next->_random = cur->_random->_next;
}
cur = cur->_next->_next;
}
//取出新链表
newList = cList->_next;
cur = cList->_next;
while(cur->_next)
{
cur->_next = cur->_next->_next;
cur = cur->_next;
}
cur->_next = NULL;
return newList;
}
}
void PushComplexList(ComplexNode** ccList, DataType x)//复杂链表的尾插法
{
ComplexNode* tmp = NULL;
if((*ccList) == NULL)
(*ccList) = BuyNode(x);
else if((*ccList)->_next == NULL)
(*ccList)->_next = BuyNode(x);
else
{
tmp = *ccList;
while(tmp->_next)
{
tmp = tmp->_next;
}
tmp->_next = BuyNode(x);
}
}