下面是C++中一些常见的单链表面试题,文中皆是无头单链表。


如下: 我们必须构建节点,为了方便测试,我只写了尾插函数。


#include<iostream>
#include<stack>
#include<assert.h>
using namespace std;

typedef struct Node
{
Node(const int& data)
: _data(data)
, _pNext(NULL)
{}
int _data;
Node *_pNext;
}*pNode;

void InitListNode(pNode *pHead)
{
assert(pHead);
*pHead = NULL;
}
void PushBack(pNode *pHead,int data)
{
pNode NewNode = new Node(data);
if (NULL == *pHead)
*pHead = NewNode;
else
{
pNode pCur = *pHead;
while (pCur->_pNext)
{
pCur = pCur->_pNext;
}
pCur->_pNext = NewNode;
}
}



1、逆序打印单链表


解决这个问题,我们可以利用栈的特性,先进后出。


void PrintListFromTail(pNode pHead)
{
stack<pNode> s; //构造栈
pNode pCur = pHead;

while (pCur)
{
s.push(pCur);
pCur = pCur->_pNext;
}

while (!s.empty())
{
cout << s.top()->_data << " ";
s.pop();
}
cout << endl;
}

下面我们用递归实现,



void PrintListFromTail(pNode pHead)
{
if (pHead)
{
PrintListFromTail(pHead->_pNext);
cout << pHead->_data << " ";
}
cout << endl;
}


2、删除一个无头单链表的非尾结点(不能遍历链表)


我们可以将下一个结点的数据复制到给定的结点中,再删除下一个节点。也因此,题目中说明是非尾的结点。


void DelNotTail(pNode pos)
{
assert(pos->_pNext);
pNode node = pos->_pNext;
pos->_data = node->_data;
pos->_pNext = node->_pNext;
delete node;
node = NULL;
}


3、 在无头单链表的一个非头节点前插入一个结点


受上题启发,我们可以插到此节点后面,再交换两个节点的内容


void InsertNode(pNode pos, int data)
{
pNode pNewNode = new Node(pos->_data);
pNewNode->_pNext = pos->_pNext;
pos->_pNext = pNewNode;
pos->_data = data;
}



4、单链表实现约瑟夫环(JosephCircle)
解释一下约瑟夫环,它是一个数学的应用问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。
从编号为1的人开始报数,数到k的那个人出列;他的下一个人又从1开始报数,数到k的那个人又出列;依此规律重复下去,直到圆桌周围的人只剩下一个人。


我们要先把链表构成环,再确定删除点。


pNode JosephCircle(pNode& pHead,int M)
{
pNode pCur = pHead; //构环
while (pCur->_pNext != NULL)
{
pCur = pCur->_pNext;
}
pCur->_pNext = pHead;
pNode pcur = pHead;
while (pcur->_pNext == pcur)
{
while (--M)
{
pcur = pcur->_pNext;
}
//删除节点
pNode del = pcur->_pNext;
pcur->_pNext = del->_pNext;
pcur->_data = del->_data;
delete del;
del = NULL;
}
pcur->_pNext = NULL;
pcur = pHead;
return pcur;
}


5、逆置/反转单链表


将整个链表逆置


void Reverse(pNode& pHead)
{
if (pHead == NULL || pHead->_pNext == NULL)
return;
pNode pNewHead = NULL;
pNode pNextCur= NULL;
pNode pCur = pHead;
while (pCur)
{
pNextCur = pCur->_pNext;
pCur->_pNext = pNewHead;
pNewHead = pCur;
pCur = pNextCur;
}
pHead = pNewHead;
}



6、单链表排序


可以用冒泡排序


void BubbleSort(pNode pHead)
{
if (pHead || pHead->_pNext)
return;
pNode pCur = pHead;
pNode Tail = NULL;
pNode pNextNode = NULL;
while (pCur->_pNext != Tail)
{
while (pCur->_pNext != Tail)
{
pNextNode = pCur->_pNext;
if (pCur->_data > pNextNode->_data)
swap(pCur->_data, pNextNode->_data);
pCur = pCur->_pNext;
}
Tail = pCur;
pCur = pHead;
}
}


7、合并两个有序链表,合并后依然有序


我们要确定头结点,必须知道两个链表首元素的大小;第二步将链表合并;第三步确定哪一个链表首先遍历结束。


pNode Merge(pNode& pHead1, pNode& pHead2)
{
if (pHead1 == NULL || pHead2 == NULL) //如果有一个链表为空
return (NULL == pHead1) ? pHead2 : pHead1;
pNode pNewHead = NULL;
pNode pCur1 = pHead1;
pNode pCur2 = pHead2;
pNode pTail = NULL;
if (pCur1->_data > pCur2->_data)
{
pNewHead = pCur2;
pTail = pNewHead;
pCur2 = pCur2->_pNext;
}
else
{
pNewHead = pCur1;
pTail = pNewHead;
pCur1 = pCur1->_pNext;
}
while (pCur1&&pCur2)
{
if (pCur1->_data > pCur2->_data)
{
pTail->_pNext = pCur2;
pCur2 = pCur2->_pNext;
pTail = pTail->_pNext;
}
else
{
pTail->_pNext = pCur1;
pCur1 = pCur1->_pNext;
pTail = pTail->_pNext;
}
}
if (NULL == pCur1)
pTail->_pNext = pCur2;
if (NULL == pCur2)
pTail->_pNext = pCur1;
return pNewHead;
}


也可以用递归解决


pNode Merge(pNode& pHead1, pNode& pHead2)
{
if (pHead1 == NULL || pHead2 == NULL) //如果有一个链表为空
return (NULL == pHead1) ? pHead2 : pHead1;
pNode pNewHead = NULL;
pNode pCur1 = pHead1;
pNode pCur2 = pHead2;
if (pCur1->_data > pCur2->_data)
{
pNewHead = pCur2;
pNewHead->_pNext = Merge(pCur1, pCur2->_pNext);
}
else
{
pNewHead = pCur1;
pNewHead->_pNext = Merge(pCur1->_pNext, pCur2);
}
return pNewHead;
}



8. 查找单链表的中间节点,要求只能遍历一次链表


可以考虑定义两个指针,一个快指针一次走两步,一个慢指针一次走一步,当快指针走到尾部,慢指针则刚好走到链表中间



  1. 如果链表是奇数个结点,刚好返回中间的结点;如果链表是偶数个结点,返回的是中间靠右的结点;
  2. 如果要返回偶数结点的左边的那个,则需要加个判断链表结点个数是偶数还是奇数的条件,如下注释部分。
pNode MiddleNode(pNode pHead)
{
if (pHead == NULL)
return NULL;
pNode pFast = pHead;
pNode pSlow = pHead;
//pNode pMid = NULL;
while ((pFast) && (pFast->_pNext ))
{
pFast = pFast->_pNext->_pNext;
//pMid = pSlow;
pSlow = pSlow->_pNext;
}
return pSlow;
//return pMid;
}


9. 查找单链表的倒数第k个节点,要求只能遍历一次链表
让快指针先走k步,再一起走,两个指针自然相差k步,当快指针走到尾,慢指针就是倒数k个。


pNode TailkNode(pNode pHead,size_t k)
{
if (pHead == NULL || k == 0)
return NULL;
pNode pFast = pHead;
pNode pSlow = pHead;
while (--k)
{
if (pFast == NULL)
return NULL;
pFast = pFast->_pNext;
}
while (pFast->_pNext)
{
pFast = pFast->_pNext;
pSlow = pSlow->_pNext;
}
return pSlow;
}


10. 删除链表的倒数第K个结点


void DelTailkNode(pNode *pHead,size_t k)
{
//找节点
if (pHead == NULL || k == 0)
return;
pNode pFast = *pHead;
pNode pSlow = *pHead;
pNode pPre = NULL;
while (k--)
{
if (pFast == NULL)
return;
pFast = pFast->_pNext;
}
while (pFast)
{
pFast = pFast->_pNext;
pPre = pSlow;
pSlow = pSlow->_pNext;
}
//删除节点
if (pSlow == *pHead)
*pHead = (*pHead)->_pNext;
else
pPre->_pNext = pSlow->_pNext;
delete pSlow;
}



11. 判断单链表是否带环?若带环,求环的长度?求环的入口点?


还是快指针与慢指针,若带环,快指针与慢指针肯定会在环上相遇,则返回相遇点。


pNode HasCircle(pNode pHead)           //判断是否带环,返回相遇点
{
if (pHead == NULL)
return NULL;
pNode pFast = pHead;
pNode pSlow = pHead;
while (pFast&&pFast->_pNext)
{
pFast = pFast->_pNext->_pNext;
pSlow = pSlow->_pNext;
if (pFast == pSlow)
return pSlow;
}
return NULL;
}



求环的长度,遍历一遍环即可。


size_t LengthCircle(pNode pHead)
{
pNode pMeet = HasCircle(pHead);
if (pHead == NULL&&pMeet == NULL)
return 0;
pNode Meet = pMeet;
size_t count = 1;
while (Meet->_pNext!= pMeet)
{
count++;
Meet = Meet->_pNext;
}
return count;
}


求环的入口点。


我们要计算一下:



C++单链表面试题_#include



pNode EnterCircle(pNode pHead)
{
pNode pMeet = HasCircle(pHead);
if (pHead == NULL&&pMeet == NULL)
return 0;
pNode pCur = pHead;
while (pMeet!=pCur)
{
pMeet = pMeet->_pNext;
pCur = pCur->_pNext;
}
return pMeet;
}


12. 判断两个链表是否相交,若相交,求交点。(假设链表不带环)

先考虑有什么情况,两个不带环的链表相交,如下图,只能是这样,且他们的最后的元素相等



C++单链表面试题_结点_02


判断是否相交:


bool IsCross(pNode pHead1,pNode pHead2)
{
if (NULL == pHead1 || NULL == pHead2)
return false;
pNode pCur1 = pHead1;
pNode pCur2 = pHead2;
while (pCur1->_pNext)
pCur1 = pCur1->_pNext;
while (pCur2->_pNext)
pCur2 = pCur2->_pNext;
if (pCur1 == pCur2)
return true;
return false;



求交点。我们可以遍历两条链表,求出长度并求出长度差;让长的先走长度差的部分;


pNode CrossNode(pNode pHead1, pNode pHead2)
{
if (!IsCross(pHead1, pHead2))
return NULL;
pNode pCur1 = pHead1;
pNode pCur2 = pHead2;
size_t count1 = 0;
size_t count2 = 0;
//求长度差
pNode tmp1 = pCur1;
pNode tmp2 = pCur2;
while (tmp1)
{
tmp1 = tmp1->_pNext;
count1++;
}
while (tmp2)
{
tmp2 = tmp2->_pNext;
count2++;
}
int count = count2 - count1;
//求交点
if (count > 0) //2比1长
{
while (count--)
{
pCur2 = pCur2->_pNext;
}
}
else //1比二长
{
while (count++)
{
pCur1 = pCur1->_pNext;
}
}
while (pCur1 != pCur2)
{
pCur1 = pCur1->_pNext;
pCur2 = pCur2->_pNext;
}
return pCur1;
}



第二种方法,将一条链表的尾连到自己的头构成环,求环的入口点。



pNode CrossNode(pNode pHead1, pNode pHead2)
{
if (IsCross(pHead1, pHead2))
return NULL;
pNode pCur1 = pHead1;
pNode pCur2 = pHead2;
while (pCur1->_pNext)
{
pCur1 = pCur1->_pNext;
}
pCur1->_pNext = pCur2;
pNode tmp = HasCircle(pHead1);
return tmp;
}


 13、判断两个链表是否相交,若相交,求交点。(假设链表可能带环)




C++单链表面试题_#include_03





判断是否相交,若带环相交则共用一个环。


bool IsCrossWithCircle(pNode pHead1, pNode pHead2)
{
if (NULL == pHead1 || NULL == pHead2)
return false;
pNode pMeet1 = HasCircle(pHead1);
pNode pMeet2 = HasCircle(pHead2);
//两个链表都不带环
if (pMeet1 == NULL&&pMeet2 == NULL)
{
pNode pCur1 = pHead1;
pNode pCur2 = pHead2;
while (pCur1->_pNext)
pCur1 = pCur1->_pNext;
while (pCur2->_pNext)
pCur2 = pCur2->_pNext;
if (pCur1 == pCur2)
return true;
return false;
}
//两个链表都带环
else if (pMeet1&&pHead2)
{
pNode tmp = pMeet1;
while (tmp->_pNext != pMeet1)
{
if (tmp == pMeet2)
return 1;
tmp = tmp->_pNext;
}
return 0;
}
return 0;
}



求交点,还是构环,求环的入口点;将一条链表的相遇点指向自己的头,如下:



C++单链表面试题_链表_04


pNode CrossNodeWithCircle(pNode pHead1, pNode pHead2)
{
pNode pMeetNode1 = HasCircle(pHead1);
pNode pMeetNode2 = HasCircle(pHead2);
pNode pTmp = pMeetNode1->_pNext;
if (pMeetNode1&&pMeetNode2)
{
pMeetNode1->_pNext = pHead2;
pNode pEnter = EnterCircle(pMeetNode1);
pMeetNode1->_pNext = pTmp;
return pEnter;
}
else if (pMeetNode1 == NULL&&pMeetNode2 == NULL)
{
pNode pEnter = CrossNode(pHead1, pHead2);
return pEnter;
}
return NULL;
}


以上函数测试如下:


void test1()
{
pNode n;
InitListNode(&n);
PushBack(&n, 1);
PushBack(&n, 2);
PushBack(&n, 3);

pNode m;
InitListNode(&m);
PushBack(&m, 4);
PushBack(&m, 5);
PushBack(&m, 6);

/*PrintListFromTail(n);

DelNotTail(n);
PrintListFromTail(n);

InsertNode((n->_pNext), 6);
PrintListFromTail(n);
*/
/*BubbleSort(n);
PrintListFromTail(n);
*/
//cout<<JosephCircle(n, 3)->_data;

//Reverse(n);
//PrintListFromTail(n);

pNode tmp = Merge(n, m);
PrintListFromTail(tmp);

cout << MiddleNode(n)->_data << endl;

//cout << TailkNode(n, 1)->_data << endl;

/*DelTailkNode(&n, 1);
PrintListFromTail(n);*/
}
void test2()
{
pNode n;
InitListNode(&n);
PushBack(&n, 1);
PushBack(&n, 2);
PushBack(&n, 3);
PushBack(&n, 4);
PushBack(&n, 5);
PushBack(&n, 6);

//构环
pNode tmp = n;
pNode cur = tmp->_pNext;
while (tmp->_pNext)
{
tmp = tmp->_pNext;
}
tmp->_pNext = cur;

/*cout << LengthCircle(n) << endl;
cout << EnterCircle(n)->_data << endl;
cout << HasCircle(n)->_data << endl;*/
}
void test3()
{
pNode n;
InitListNode(&n);
PushBack(&n, 1);
PushBack(&n, 2);
PushBack(&n, 3);
PushBack(&n, 4);

pNode m;
InitListNode(&m);
PushBack(&m, 4);
PushBack(&m, 5);
PushBack(&m, 6);
PushBack(&m, 7);
pNode tmp = n;
while (tmp->_pNext)
{
tmp = tmp->_pNext;
}
tmp->_pNext = m->_pNext;

cout << IsCross(n, m) << endl;
cout << CrossNode(n, m)->_data << endl;
}
void test4()
{
pNode n;
InitListNode(&n);
PushBack(&n, 1);
PushBack(&n, 2);
PushBack(&n, 3);
PushBack(&n, 4);
PushBack(&n, 5);
PushBack(&n, 6);

//构环
pNode tmp1 = n;
pNode cur1 = tmp1->_pNext;
while (tmp1->_pNext)
{
tmp1 = tmp1->_pNext;
}
tmp1->_pNext = cur1;


pNode m;
InitListNode(&m);
PushBack(&m, 1);
PushBack(&m, 4);
PushBack(&m, 5);
PushBack(&m, 6);
PushBack(&m, 7);

//构环
pNode tmp2 = m;
while (tmp2->_pNext)
{
tmp2 = tmp2->_pNext;
}
tmp2->_pNext = cur1;

cout << IsCrossWithCircle(n, m) << endl;
cout << CrossNodeWithCircle(n, m)->_data << endl;
}


在这里简单介绍如何测试,就不加运行结果了,读者可以自行测试。




14. 复杂链表的复制
一个链表的每个节点,有一个指向next指针指向下一个节点,还有一个random指针指向这个链表中的一个随机节点或者NULL,现在要求实现复制这个链表,返回复制后的新链表。



第一步:将原链表的每一个结点都复制一份,然后连接在一起。如图所示:绿色为随机指针的连接,黑色为Next指针的指向。如下图:




C++单链表面试题_结点_05



第二步:处理链表底下一排的新链表的随机指针的连接。如上图,下面为新链表,新链表的随机指针指向为旧链表指向的next,即:


pnew->_pRomdon = pold->_pRomdon->_pNext,如下图:



C++单链表面试题_#include_06




第三步:将新链表分离出来,如下图:

C++单链表面试题_结点_07



代码如下,构造节点:



typedef struct Node
{
Node(const int& data)
:_data(data)
, _pNext(NULL)
, _pRomdon(NULL)
{}

int _data;
Node* _pNext;
Node* _pRomdon;
}*pNode;

void InitListNode(pNode *pHead)
{
assert(pHead);
*pHead = NULL;
}
void PushBack(pNode *pHead, int data)
{
pNode NewNode = new Node(data);
if (NULL == *pHead)
*pHead = NewNode;
else
{
pNode pCur = *pHead;
while (pCur->_pNext)
{
pCur = pCur->_pNext;
}
pCur->_pNext = NewNode;
}
}


为了方便测试,我增加了寻找函数和打印函数,如下代码:


pNode find(pNode *pHead,int k)
{
if (pHead == NULL)
return NULL;
pNode pCur = *pHead;
while (pCur)
{
if (pCur->_data == k)
return pCur;
pCur = pCur->_pNext;
}
return NULL;
}
pNode ComplexListCopy(pNode pHead)
{
if (NULL == pHead)
return NULL;
pNode pOld = pHead;
//第一步:复制节点
while (pOld)
{
pNode pNewNode = new Node(pOld->_data);
pNewNode->_pNext = pOld->_pNext;
pOld->_pNext = pNewNode;
pOld = pOld->_pNext->_pNext;
}
pOld = pHead;
pNode pNew = pOld->_pNext;
//第二步:连随机指针
while (pNew->_pNext)
{
if (pOld->_pRomdon)
pNew->_pRomdon = pOld->_pRomdon->_pNext;
pOld = pNew->_pNext;
pNew = pOld->_pNext;
}
pOld = pHead;
pNew = pHead->_pNext;
//第三步:分离
pNode pCur = pNew;
while (pCur->_pNext)
{
pOld->_pNext = pCur->_pNext;
pOld = pCur->_pNext;
pCur->_pNext = pOld->_pNext;
pCur = pCur->_pNext;
}
return pNew;
}
void Print(pNode pHead)
{
pNode pCur = pHead;
while (pCur)
{
cout << pCur->_data << " ";
pCur = pCur->_pNext;
}
cout << endl;
}



测试函数如下:


void test()
{
pNode n;
InitListNode(&n);
PushBack(&n, 1);
PushBack(&n, 2);
PushBack(&n, 3);
PushBack(&n, 4);

Print(n);

find(&n, 1)->_pRomdon = find(&n, 3);
find(&n, 2)->_pRomdon = find(&n, 1);
find(&n, 3)->_pRomdon = find(&n, 3);
find(&n, 4)->_pRomdon = NULL;

pNode m = ComplexListCopy(n);
Print(m);

}



运行结果:



C++单链表面试题_结点_08