一下面试题中要用到的数据类型以及一些函数。
typedef int DataType;
typedef struct SLinkList
{
DataType data;
struct SLinkList*PNext;
}SNode;
// 初始化
void SLinkList_InIt(SNode **Phead)
{
assert(Phead != NULL);
*Phead = NULL;
}
//创建一个新结点
static SNode* BuySListNode(DataType data)
{
SNode *pNewNode = (SNode*)malloc(sizeof(SNode));
if (pNewNode == NULL)
{
assert(0);
return NULL;
}
pNewNode->data = data;
pNewNode->PNext = NULL;
return pNewNode;
}
//头部插入
void PushFront(SNode**Phead, DataType data)
{
assert(Phead != NULL);
if (*Phead == NULL)
{
*Phead = BuySListNode(data);
return;
}
else
{
SNode *pNewNode = BuySListNode(data);
pNewNode->PNext = *Phead;
*Phead = pNewNode;
}
}
//打印链表
void SLinkList_Print(SNode *Phead)
{
assert(Phead != NULL);
while (Phead != NULL)//递归结束条件
{
printf("%d ", Phead->data);
Phead = Phead->PNext;
}
printf("\n");
}
SNode* Find(SNode*Phead, DataType data)//查找指定节点,找到便返回该节点
{
assert(Phead != NULL);
if (Phead == NULL)
{
return NULL;
}
else
{
SNode*pNewNode = Phead;
for (pNewNode = Phead; pNewNode != NULL; pNewNode = pNewNode->PNext)
{
if (pNewNode->data == data)
{
return pNewNode;
}
}
}
return NULL;
}
1.判断两个链表是否相交,若相交,求交点。(假设链表不带环)
思路:
先来画一画两个链表相交有哪些情况?
考虑以上三种情况整理一下思路。
- 先判断两个的长度是否相等
- 让长的链表先往前走两个链表长度的差值步,和另外一个链表起点相同
- 两个链表一起同时往后走,若相遇就是相交,若没有就不相交
int SListLength(SNode*Phead)//求链表的长度
{
assert(Phead != NULL);
SNode*PCur = Phead;
int len = 0;
while (PCur != NULL)
{
PCur = PCur->PNext;
len++;
}
return len;
}
SNode* IsListCross(SNode*Phead1, SNode*Phead2)//判断链表是否相交
{
SNode*PL1 = Phead1;
SNode*PL2 = Phead2;
int len1 = SListLength(Phead1);
int len2 = SListLength(Phead2);
int diff = len1 - len2;
//如果两个链表的长度不一样,将较长的链表截到和另外一条链表的长度相等
if (diff>0)
{
while (diff--)
{
PL1 = PL1->PNext;
}
}
else
{
while (diff++)
{
PL2 = PL2->PNext;
}
}
//同时遍历两个链表并比较它们是否相同
while (PL1 != NULL&&PL2 != NULL)
{
if (PL1 == PL2)
{
return PL1;
}
else
{
PL1 = PL1->PNext;
PL2 = PL2->PNext;
}
}
return NULL;
}
测试用例:
void Test()
{
SNode* PFirst1;
SNode*PFirst2;
SLinkList_InIt(&PFirst1);
SLinkList_InIt(&PFirst2);
PushFront(&PFirst1, 7);
PushFront(&PFirst1, 6);
PushFront(&PFirst1, 5);
PushFront(&PFirst1, 4);
PushFront(&PFirst1, 3);
PushFront(&PFirst1, 2);
PushFront(&PFirst1, 1);
SLinkList_Print(PFirst1);
PushFront(&PFirst2, 7);
PushFront(&PFirst2, 6);
PushFront(&PFirst2, 5);
PushFront(&PFirst2, 4);
PushFront(&PFirst2, 3);
PushFront(&PFirst2, 2);
PushFront(&PFirst2, 1);
SLinkList_Print(PFirst2);
SNode*PRet = Find(PFirst1, 5);
PFirst2->PNext = PRet;
SNode* Pret = IsListCross(PFirst1, PFirst2);
if (Pret == NULL)
{
printf("不相交\n");
}
else
{
printf("相交,交点为%d\n", Pret->data);
}
}
2.判断单链表是否带环?若带环,求环的长度?求环的入口点?
这里我们可以用到之前的快慢指针的方法,若链表带环的话快指针先进环,慢指针最后肯定会和它相遇(如果这样子讲想不通的话,可以想想长跑比赛的时候跑的最快的那个肯定会和跑的慢的那个相遇,因为速度快的会追上慢的),如果不带环的话,那当快指针指向NULL的时候让两个指针都停止遍历,所以慢指针和快指针永远不会相遇。
具体思路:
1.判断链表是否带环
- 快慢指针,快指针走两步慢指针走一步(这里快指针走三步或者其余步数时,当快指针和慢指针的步数差,刚好等于环的长度时,是永远不会相遇的,特殊例子:当环只有两个节点的时候,快指针走3步,慢指针走2步,就会永远的擦肩而过,永远都不会相遇)
- 当快指针和慢指针相遇的话就是有环,否则没有,相遇是快慢指针的任一个就是他们的相遇点
- 让指针从快慢指针的相遇点往后走,同时len++.若是等于相遇点就停下来,刚好走了环的一圈的长度
- 让一个指针从链表的起始位置往后面走,一个指针从快慢指针的相遇点往后走(在圈里走),最终两个指针相遇的节点就是环的入口点
SNode*HasCircle(SNode*PFirst)//判断是否带环,并求相遇点
{
assert(PFirst != NULL);
if (PFirst == NULL)
{
return NULL;
}
SNode*PFast = PFirst;
SNode*PSlow = PFirst;
while (PFast->PNext != NULL&&PFast->PNext->PNext != NULL)
{
PSlow = PSlow->PNext;
PFast = PFast->PNext->PNext;
if (PFast == PSlow)
{
return PFast;
}
}
return NULL;
}
int GetCircleLength(SNode*PMeet)//求环的长度
{
SNode*Pnode = PMeet->PNext;//因为循环的条件是Pnode!=PMeet,如果初始化为PMeet就无法进入循环,所以从PMeet的下一个节点开始
int len = 1;//因为从PMeet的下一个节点开始,错过了一个节点,因此len要从1开始
while (Pnode != PMeet)
{
Pnode = Pnode->PNext;
len++;
}
return len;
}
SNode*GetCirclePoint(SNode*Phead, SNode*PMeet)//求环的入口点
{
SNode*PStart = Phead;
SNode*PMeetStart = PMeet;
while (PStart != PMeetStart)
{
PStart = PStart->PNext;
PMeetStart = PMeetStart->PNext;
}
return PStart;
}
测试用例:
void Test()
{
SNode* PFirst1;
SLinkList_InIt(&PFirst1);
PushFront(&PFirst1, 6);
PushFront(&PFirst1, 5);
PushFront(&PFirst1, 4);
PushFront(&PFirst1, 3);
PushFront(&PFirst1, 2);
PushFront(&PFirst1, 1);
SNode*PRet = Find(PFirst1, 6);
SNode*P = Find(PFirst1, 3);
PRet->PNext = P;
SNode*P1 = HasCircle(PFirst1);
if (P1 == NULL)
{
printf("no circle\n");
}
else
{
printf("have circle\n");
printf("The length of circle is :%d\n", GetCircleLength(P1));
printf("The entry point of circle is :%d\n", GetCirclePoint(PFirst1, P1)->data);
}
}
原理图:
3.判断两个链表是否相交,若相交求交点。(假设链表可能带环)
这道题相较于第一道题难度增加了一丢丢,因为要考虑到链表带环的问题。同样我们先画一画它可能出现的几种情况。
出现的情况就以上六种,从相交不相交考虑,带环不带环考虑。
思路:
1. 先判断链表是否带环
- 当两条链表都不带环,这时将问题转换成为不带环的两条链表的相交问题
- 如果一条带环一条不带环分别求它们的入环点,如果入环点相同的话,将环拆开,将问题转换成为两个不带环的链表求是否相交的问题,最后再将环恢复
- 如果两个链表都带环,分别求它们的环的长度,
- 如果不相等不相交
- 如果相等的话令一个指针从一个链表的入口点为起始点在环里走,如果遇到了另外一条链表的起始点就相交,分别返回两条链表的入口点,否则不相交。
SNode*HasCircle(SNode*PFirst)//判断是否带环,并求相遇点
{
assert(PFirst != NULL);
if (PFirst == NULL)
{
return NULL;
}
SNode*PFast = PFirst;
SNode*PSlow = PFirst;
while (PFast->PNext != NULL&&PFast->PNext->PNext != NULL)
{
PSlow = PSlow->PNext;
PFast = PFast->PNext->PNext;
if (PFast == PSlow)
{
return PFast;
}
}
return NULL;
}
int GetCircleLength(SNode*PMeet)//求环的长度
{
SNode*Pnode = PMeet->PNext;
while (Pnode != PMeet)
{
Pnode = Pnode->PNext;
len++;
}
return len;
}
SNode*GetCirclePoint(SNode*Phead, SNode*PMeet)//求环的入口点
{
SNode*PStart = Phead;
SNode*PMeetStart = PMeet;
while (PStart != PMeetStart)
{
PStart = PStart->PNext;
PMeetStart = PMeetStart->PNext;
}
return PStart;
}
int IsCrossMayBeCrile(SNode*PFirst1, SNode*PFirst2, SNode**Ppnode1, SNode**Ppnode2)
{
if (PFirst1 == NULL || PFirst2 == NULL)
{
return 0;
}
*Ppnode1 = NULL;
*Ppnode2 = NULL;
SNode*Pmeet1 = HasCircle(PFirst1);
SNode*Pmeet2 = HasCircle(PFirst2);
SNode*hc1 = HasCircle(PFirst1);
SNode*hc2 = HasCircle(PFirst2);
if (hc1 == NULL&&hc2 == NULL)//两条不带环的链表
{
if (IsListCross(PFirst1, PFirst2) == NULL)//转换成求两条不带环的链表是否相交的问题,这里用到了我们上面的函数
{
return 0;
}
*Ppnode1 = IsListCross(PFirst1, PFirst2);
return 1;
}
if ((hc1 == NULL&&hc2 != NULL) || (hc1 != NULL&&hc2 == NULL))//一条带环一条不带环
{
return 0;
}
SNode*CirclePoint1 = GetCirclePoint(PFirst1, Pmeet1);//求环的入口点
SNode*CirclePoint2 = GetCirclePoint(PFirst2, Pmeet2);
if (CirclePoint1 == CirclePoint2)//环的入口点相同
{
SNode*Pnext = CirclePoint1->PNext;//拆环,转换成两条不带环的链表的相交问题
CirclePoint1->PNext = NULL;
*Ppnode1 = IsListCross(PFirst1, PFirst2);
CirclePoint1->PNext = Pnext;//恢复环
return 1;
}
int len1 = GetCircleLength(Pmeet1);
int len2 = GetCircleLength(Pmeet2);
if (len1 != len2)//两条链表都带环,环长不相等肯定不相交
{
return 0;
}
int i = 0;
SNode*Pcur = CirclePoint1;
for (i = 0; i < len1; i++)//如果环长相等,令一个指针从一个链表的入口点为起始点在环里走,如果遇到了另外一条链表的起始点就相交分别返回两条链表的入口点
{
if (Pcur == CirclePoint2)
{
*Ppnode1 = CirclePoint1;
*Ppnode2 = CirclePoint2;
return 1;
}
Pcur = Pcur->PNext;
}
return 0;
}
测试用例:
void TestIsCrossMayBeCrile()
{
SNode* PFirst1;
SNode*PFirst2;
SLinkList_InIt(&PFirst1);
SLinkList_InIt(&PFirst2);
PushFront(&PFirst1, 7);
PushFront(&PFirst1, 5);
PushFront(&PFirst1, 6);
PushFront(&PFirst1, 4);
PushFront(&PFirst1, 3);
PushFront(&PFirst1, 2);
PushFront(&PFirst1, 1);
PushFront(&PFirst2, 7);
PushFront(&PFirst2, 5);
PushFront(&PFirst2, 6);
PushFront(&PFirst2, 4);
PushFront(&PFirst2, 3);
PushFront(&PFirst2, 2);
PushFront(&PFirst2, 1);
#if 0//无环单链表相交
SNode *p7 = Find(PFirst2, 7);
SNode *p3 = Find(PFirst1, 3);
p7->PNext = p3;
SLinkList_Print(PFirst1);
SLinkList_Print(PFirst2);
#endif
#if 0//一个有环一个没有环不相交
SNode*P7 = Find(PFirst2, 7);
SNode*P3 = Find(PFirst2, 5);
P7->PNext = P3;
#endif
#if 0 //一个有环,一个没有环,相交
SNode*P7 = Find(PFirst1, 7);
SNode*P3 = Find(PFirst1, 5);
P7->PNext = P3;
SNode*P4 = Find(PFirst2, 7);
SNode*P5 = Find(PFirst1, 3);
P4->PNext = P5;
#endif
#if 1 //两个链表都有环并且入环点不一样
SNode *p7 = Find(PFirst1, 7);
SNode *p3 = Find(PFirst1, 3);
p7->PNext = p3;
SNode *p12 = Find(PFirst1, 5);
SNode *p27 = Find(PFirst2, 7);
p27->PNext = p12;
#endif
#if 1
SNode*P1 = NULL;
SNode*P2 = NULL;
int r = IsCrossMayBeCrile(PFirst1, PFirst2, &P1, &P2);
if (r == 0)
{
printf("不相交\n");
}
else
{
printf("相交\n");
if (P1 != NULL&&P2 != NULL)
{
printf("有两个交点\n");
printf("第一个交点为 :%d\n", P1->data);
printf("第二个交点为 :%d\n", P2->data);
}
if (P1 != NULL&&P2 == NULL)
{
printf("只有一个交点,且交点为:%d\n", P1->data);
}
}
#endif
}
4.复杂链表的复制。一个链表的每一个节点,有一个指向next指针指向下一个节点,还有一个random指针指向这个链表中的一个随机节点或者NULL,现在要求实现复制这个链表,返回复制后的新链表。
思路:
- 新建节点复制旧的节点的数据,插入到旧节点的后面,构成一个新的链表
- 复制旧节点random的指向
- 断开旧链表与新链表
- 重新连接就链表和新链表,返回新的链表
typedef struct CList
{
DataType data;
struct CList*Pnext;
struct CList*Prandom;
}CList;
void InitCList(CList**PFirst)
{
*PFirst = NULL;
}
void CListPushFront(CList**Pfirst, DataType data)
{
CList* PNewNode = (CList*)malloc(sizeof(CList));
PNewNode->data = data;
PNewNode->Pnext = *Pfirst;
PNewNode->Prandom = NULL;
*Pfirst = PNewNode;
}
CList* CListFind(CList*Pfirst, DataType data)
{
assert(Pfirst != NULL);
if (Pfirst == NULL)
{
return NULL;
}
CList*PNode = Pfirst;
while (PNode != NULL)
{
if (PNode->data == data)
{
return PNode;
}
PNode = PNode->Pnext;
}
return NULL;
}
void CListPrint(CList*Pfirst)
{
assert(Pfirst != NULL);
if (Pfirst == NULL)
{
return;
}
CList*PNode = Pfirst;
for (PNode = Pfirst; PNode != NULL; PNode = PNode->Pnext)
{
printf("%d (%d) ->", PNode->data,PNode->Prandom?PNode->Prandom->data:0);
}
printf("NULL\n");
}
void DestroyCList(CList**PFirst)
{
CList*PNode = *PFirst;
CList*Pnext = NULL;
while (PNode != NULL)
{
Pnext = PNode->Pnext;
free(PNode);
PNode = Pnext;
}
*PFirst = NULL;
}
CList* CopyCList(CList*Pfirst)
{
assert(Pfirst != NULL);
if (Pfirst == NULL)
{
return NULL;
}
CList*PCur = Pfirst;
CList*PNode = NULL;
CList*PNew = NULL;
for (PCur = Pfirst; PCur != NULL;PCur=PCur->Pnext->Pnext)//复制旧链表中的数据并插入到旧链表每个节点的后面构成新的链表
{
PNode = (CList*)malloc(sizeof(CList));
PNode->data = PCur->data;
PNode->Pnext = PCur->Pnext;
PNode->Prandom = NULL;
PCur->Pnext = PNode;
}
for (PCur = Pfirst; PCur != NULL; PCur = PCur->Pnext->Pnext)//复制旧链表中random的值
{
if (PCur->Prandom != NULL)
{
PCur->Pnext->Prandom = PCur->Prandom->Pnext;//复制random
}
}
CList*PCopyNode = NULL;
CList*Pnext = NULL;
PNew = Pfirst->Pnext;//保存新链表的头,防止拆开链表后头丢失
for (PCur = Pfirst; PCur != NULL; PCur = PCur->Pnext)//将新旧链表拆开,重新组合
{
PCopyNode = PCur->Pnext;
Pnext = PCopyNode->Pnext;
if (Pnext != NULL)
{
PCopyNode->Pnext = Pnext->Pnext;
}
else
{
PCopyNode->Pnext =NULL;
}
PCur->Pnext = Pnext;
}
return PNew;
}
测试用例:
void CListCopy()
{
CList*PFirst;
InitCList(&PFirst);
CListPushFront(&PFirst,11);
CListPushFront(&PFirst,10);
CListPushFront(&PFirst, 9);
CListPushFront(&PFirst, 8);
CListPushFront(&PFirst, 7);
CListPushFront(&PFirst, 6);
CListPushFront(&PFirst, 5);
CListPushFront(&PFirst, 4);
CListPushFront(&PFirst, 3);
CListPushFront(&PFirst, 2);
CListPushFront(&PFirst, 1);
CList*P1 = CListFind(PFirst, 11);
CList*P2 = CListFind(PFirst, 8);
CList*P3 = CListFind(PFirst, 6);
CList*P4 = CListFind(PFirst, 5);
CList*P5 = CListFind(PFirst, 3);
CList*P6 = CListFind(PFirst, 1);
P1->Prandom = P3;
P2->Prandom = P3;
P3->Prandom = P6;
P5->Prandom = NULL;
CListPrint(PFirst);
CList*PRet = CopyCList(PFirst);
DestroyCList(&PFirst);
printf("拆链子之后\n");
CListPrint(PRet);
}