在这里给出一些关于单链表常见的面试题。
一、 给定单链表,检测是否有环。
1.题目分析
仔细读题目发现还是有难度的,很多人刚开始会理解成就是判断一个单链表是否为循环链表,这样的理解是错的。题目的意思是指给出一条单链表,判断其中是否含有环,如“6”字型单链表就含有环。
明白题目意思以后我们来说明解题思路,假设给定的单链表中有n个节点,我们可以利用两个指针fast和slow来求解问题。fast指针每次走两步,而slow指针每次走一步,下面分两种情况来说明。
第一种情况是链表中没有环,即普通的单链表,按上面的规则fast和slow一直往链表后面走,这时候会特殊情况,就是fast指针本应该每次走两步,但走完一步以后就走到了链表的尾部,此时fast指针为NULL,不再往后走;而slow指针每次走一步,这种情况下也会走到链表尾部,slow指针变为NULL,两者相等,而且这两个指针最后都变为NULL。
在第三次中fast走了一步以后就到了链表尾部,所以不再向后走,此时就静待slow的到来,看来这次的龟兔赛跑兔子没有中间睡懒觉,哈哈。
第二种情况就是链表中有环,以“6”字型链表进行说明。此时在链表进入环之前,fast指针和slow指针走过的路程都是一样的;在进入环以后两者肯定会相遇,下面给出数学证明:
2.代码实现
#ifndef JUDGE_CIRCLE_H_
#define JUDGE_CIRCLE_H_
typedef struct Node
{
int iData;
struct Node *pNext;
}Node, *List;
void InitList(List plist); //初始化单链表
bool JudgeCircle(List plist); //判断链表是否含环
#endif
#include "JudgeCircle.h"
#include
#include
void InitList(List plist)
{
if (NULL == plist) //安全性检查,下同
{
return;
}
plist->pNext = NULL;
}
bool JudgeCircle(List plist)
{
Node *pFast, *pSlow;
pSlow = plist->pNext; //共同的起点
pFast = plist->pNext;
if (pFast != NULL)
{
pFast = pFast->pNext;
}
while (pFast != pSlow)
{
pSlow = pSlow->pNext; //走一步
if (pFast != NULL)
pFast = pFast->pNext;
if (pFast != NULL)
{
pFast = pFast->pNext;
}
}
if (pSlow == pFast && pSlow != NULL)
{
return true;
}
else if (pSlow == pFast && pSlow == NULL)
{
return false;
}
return true;
}
int main(void)
{
Node head;
InitList(&head);
Node no1;
no1.iData = 1;
no1.pNext = NULL;
Node no2;
no2.iData = 2;
no2.pNext = NULL;
Node no3;
no3.iData = 3;
no3.pNext = NULL;
Node no4;
no4.iData = 4;
no4.pNext = NULL;
Node no5;
no5.iData = 5;
no5.pNext = NULL;
Node no6;
no6.iData = 6;
no6.pNext = NULL;
head.pNext = &no1;
no1.pNext = &no2;
no2.pNext = &no3;
no3.pNext = &no4;
no4.pNext = &no5;
no5.pNext = &no6;
no6.pNext = NULL;
if (JudgeCircle(&head))
{
printf("The list is a circle.\n");
}
else
{
printf("The list isn't a circle.\n");
}
return 0;
}
二、 给定两个单链表(head1, head2),检测两个链表是否有交点,如果有返回第一个交点
1.题目分析
这个题目相对比较简单,这里直接说出解题思路。
分别获取两个单链表的长度,求出两个单链表的长度差,假设单链表head1的长度为5,单链表head2的长度为3,两者的长度差为2。知道长度差以后,用一个指针指向长的那个单链表(这个例子中是指向head1),这个指针先往后走长度差步(这个例子中是2步);用一个指针指向head2,然后指向这两个链表的指针同时往后走,每次走一个节点,每走一步就要判断这两个指针的next域是是否一样,且不为NULL。如果一样且不为空,则此时找到两个链表的交点,反之。
2.代码实现
#ifndef JUDGE_CIRCLE_H_
#define JUDGE_CIRCLE_H_
typedef struct Node
{
int iData;
struct Node *pNext;
}Node, *List;
void InitList(List plist);
Node* BuyNode(int iVal);
void TailInsert(List plist, int iVal);
int GetListLen(List plist);
void PrintList(List plist);
void MergeList(List pDest, List pSrc);
Node* JudgeY(List pLhs, List pRhs);
#endif
#include "JudgeCircle.h"
#include
#include
void InitList(List plist)
{
if (NULL == plist) //安全性检查,下同
{
return;
}
plist->pNext = NULL;
}
//申请一个值为iVal的节点,并返回其内存地址
Node* BuyNode(int iVal)
{
Node* pNew = (Node*)malloc(sizeof(Node));
pNew->iData = iVal;
pNew->pNext = NULL;
return pNew;
}
void TailInsert(List plist, int iVal)
{
if (NULL == plist)
{
return;
}
Node *pTail;
for (pTail = plist; pTail->pNext != NULL; pTail = pTail->pNext)
{
NULL;
}
Node *pNew = BuyNode(iVal);
pTail->pNext = pNew;
}
int GetListLen(List plist)
{
if (NULL == plist)
{
return -1;
}
int iCount = 0;
for (Node *pTemp = plist->pNext; pTemp != NULL; pTemp = pTemp->pNext)
{
++iCount;
}
return iCount;
}
void PrintList(List plist)
{
for (Node *pTemp = plist->pNext; pTemp != NULL; pTemp = pTemp->pNext)
{
printf("%5d", pTemp->iData);
}
printf("\n");
}
void MergeList(List pDest, List pSrc)
{
if (NULL == pDest || NULL == pSrc)
{
return;
}
找到pDest的最后一个节点,然后将pSrc链接到后面
Node *pTail;
for (pTail = pDest; pTail->pNext != NULL; pTail = pTail->pNext)
{
NULL;
}
pTail->pNext = pSrc->pNext;
}
Node* JudgeY(List pLhs, List pRhs)
{
int iLhsLen = GetListLen(pLhs);
int iRhsLen = GetListLen(pRhs);
Node *pList1 = pLhs->pNext;
Node *pList2 = pRhs->pNext;
int iDiff = iLhsLen - iRhsLen;
if (iDiff > 0)
{
for (int i = 0; i < iDiff; ++i)
{
pList1 = pList1->pNext;
}
}
else
{
for (int i = 0; i < iDiff; ++i)
{
pList2 = pList2->pNext;
}
}
while (pList1 != NULL)
{
if (pList1->pNext == pList2->pNext)
{
return pList1->pNext;
}
pList1 = pList1->pNext;
pList2 = pList2->pNext;
}
return NULL;
}
int main(void)
{
Node head1, head2, head3;
InitList(&head1);
InitList(&head2);
InitList(&head3);
for (int i = 0; i < 5; ++i)
{
TailInsert(&head1, i);
}
PrintList(&head1);
for (int i = 5; i < 10; ++i)
{
TailInsert(&head2, i);
}
PrintList(&head2);
for (int i = 5; i < 10; ++i)
{
TailInsert(&head3, i + 5);
}
PrintList(&head3);
MergeList(&head1, &head3);
PrintList(&head1);
MergeList(&head2, &head3);
PrintList(&head2);
Node *p = JudgeY(&head1, &head2);
if (p != NULL)
{
printf("%d\n", p->iData);
}
else
{
printf("Not cross.");
}
return 0;
}
三、只给定单链表中某个结点p(并非最后一个结点,即p->next!=NULL)指针,删除该结点。
1.题目分析
办法很简单,首先是放p中数据,然后将p->next的数据copy入p中,接下来删除p->next即可。
2.代码实现
//删除链表中指定节点p
void DeleteNode(List plist, Node *p)
{
if(NULL == plist)
{
return;
}
Node *pTemp = p->pNext;
p->iData = pTemp ->iData;
p->pNext = pTemp ->pNext;
free(pTemp);
pTemp = NULL;
}
四、只给定单链表中某个结点p(非空结点),在p前面插入一个结点。
1.题目分析
办法与前者类似,首先分配一个结点q,将q插入在p后,接下来将p中的数据copy入q中,然后再将要插入的数据记录在p中。
2.代码实现
void InsertNode(Node *p, Node *q)
{
q->pNext = p->pNext;
p->pNext = q;
int iTemp = p->iData;
p->iData = q->iData;
q->iData = iTemp;
}
五、给定单链表头结点,删除链表中倒数第k个结点。
1.题目分析
使用两个节点p1,p2,p1初始化指向头结点,p2一直指向p1后第k个节点,两个结点平行向后移动直到p2到达链表尾部(NULL),然后根据p1删除对应结点。
2.代码实现
//查找倒数第K个节点
Node* SearchTailK(List plist, int k)
{
if (NULL == plist)
{
printf("The list is valid.\n");
return false;
}
if (k < 1 || k > GetLength(plist))
{
printf("The delete position is valid.\n");
return false;
}
Node *p1 = plist;
int iCount;
for (iCount = 0; iCount < k - 1; ++iCount)
{
p1 = p1->next;
}
Node *p2 = plist;
while (p1->next != NULL)
{
p2 = p2->next;
p1 = p1->next;
}
return p2;
}
//删除倒数第K个节点
bool DeleteTailK(List plist, int k)
{
if (NULL == plist)
{
printf("The list is valid.\n");
return false;
}
if (k < 1 || k > GetLength(plist))
{
printf("The delete position is valid.\n");
return false;
}
Node *pTemp = SearchTailK(plist, k);
Node *pPre = NULL;
for (pPre = plist; pPre->next != pTemp; pPre = pPre->next)
{
NULL;
}
pPre->next = pTemp->next;
free(pTemp);
return true;
}