链表相关面试题(二)

本文是对http://blog.csdn.net/qq_35524916/article/details/66530326的补充


本文将要解决的面试题:

// 实现单链表的逆置:使用三个指针
PNode ReverseList(PNode pHead);

// 实现单链表的逆置:使用头插法
void ReverseList_P(PNode* pHead);

// 合并两个已序单链表,合并后依然有序
PNode MergeList(PNode pHead1, PNode pHead2);

// 判断两个单链表是否相交(链表不带环)
int IsListCross(PNode L1, PNode L2);

// 若不带环的单链表相交,求交点
PNode GetCrossNode(PNode PL1, PNode PL2);

// 判断链表是否带环,若带环给出相遇点
PNode HasCircle(PNode pHead);

// 求环的长度
size_t  GetCircleLen(PNode pMeetNode);

// 求环的入口点
PNode GetEnterNode(PNode pHead, PNode pMeetNode);


1,实现单链表的逆置:使用三个指针

解析:利用pPre、pCur、pNext三个节点整体向后遍历,每遍历一次,改变三个的相对指向,即可。

注:链表为空,或只有一个节点。最后一个节点还需改变下一节点指向。

// 实现单链表的逆置:使用三个指针
PNode ReverseList(PNode pHead)
{
	PNode pPreNode = NULL;//要移动节点的前一节点
	PNode pCurNode = NULL;//要移动的节点
	PNode pNextNode = NULL;//要移动节点的后一节点

	//当链表为空或只有一个节点时,不需逆置,直接返回
	if ((NULL == pHead) || (NULL == pHead->_pNext))
		return pHead;

	pPreNode = pHead;
	pCurNode = pPreNode->_pNext;
	pNextNode = pCurNode->_pNext;

	//遍历到链表尾
	while (NULL != pCurNode->_pNext)
	{	//改变pCurNode->_pNext指向
		pCurNode->_pNext = pPreNode;
		pPreNode = pCurNode;
		pCurNode = pNextNode;
		pNextNode = pNextNode->_pNext;
	}
	//剩余最后一个节点改变指向
	pCurNode->_pNext = pPreNode;
	//原链表的头结点指向空
	pHead->_pNext = NULL;
	//头指针指向原链表的尾节点
	pHead = pCurNode;

	return pHead;
}


2,实现单链表的逆置:使用头插法

解析:思想比较简单,一个指针每遍历一个节点,就将当前节点头插在头指针前面。

注:指针为空。

// 实现单链表的逆置:使用头插法
void ReverseList_P(PNode *pHead)
{
	PNode pPreNode = NULL;

	assert(NULL != pHead);

	//当链表为空时,返回NULL
	if (NULL == *pHead)
		return ;

	pPreNode = *pHead;
	
	while (NULL != pPreNode->_pNext)
	{
		//当前要移到开头的节点
		PNode pCurNode = pPreNode->_pNext;
		//跳过pCurNode节点
		pPreNode->_pNext = pCurNode->_pNext;
		//移到开头
		pCurNode->_pNext = *pHead;
		//重置头结点
		*pHead = pCurNode;
	}
}


3,合并两个已序(从小到大)单链表,合并后依然有序

解析:首先,定义一个新的头结点pNewNode,然后指向两个链表中头结点小的节点。比较两个链表PL1,PL2当前节点的大小。

如果PL1->_data > PL2->_data,则pNewNode的下一节点指针指向PL2,PL2往后移一步。一次类推。直到有一个链表遍历完成,pNewNode直接指向另一链表。

注:链表为空。

在pNewNode的下一节点指针指向某一链表中的节点时,要先保存该节点的地址,然后链表往后移一步,pNewNode下一节点在指向保存的节点。避免陷入死循环。

// 合并两个已序(从小到大)单链表,合并后依然有序
PNode MergeList(PNode pHead1, PNode pHead2)  
{   
	PNode PL1 = pHead1;
	PNode PL2 = pHead2;
	PNode pNewHead = NULL;//合并后链表的头结点
	PNode pNewNode = NULL;//加入的新节点

	if (NULL == pHead1)
		return pHead2;
	else if (NULL == pHead2)
		return pHead1;

	//设置新的头结点指向。
	if (PL1->_data < PL2->_data)
	{
		pNewHead = pNewNode = PL1;
		PL1 = PL1->_pNext;
	}
	else
	{
		pNewHead = pNewNode = PL2;
		PL2 = PL2->_pNext;
	}
	
	//当PL1和PL2都未遍历完
	while (PL1 && PL2)
	{
		if (PL1->_data < PL2->_data)
		{	//关键点:设置一个临时节点,PL1先自加,然后pNewNode->_pNext指向tmep,添加新节点。
			//不应该先指向后加,这样会陷入死循环
			PNode temp = PL1;
			PL1 = PL1->_pNext;
			pNewNode->_pNext = temp;
		}
		else
		{
			PNode temp = PL2;
			PL2 = PL2->_pNext;
			pNewNode->_pNext = temp;
		}

		pNewNode = pNewNode->_pNext;
	}
	
	//如果还有链表没有遍历完,则将pNewNode->_pNext指向该链表,
	//添加该链表的剩余部分。
	if (PL1)
		pNewNode->_pNext = PL1;
	else
		pNewNode->_pNext = PL2;
	
	//返回新链表的头指针
	return pNewHead;
}


4,判断两个单链表是否相交(链表不带环)

解析:若是两个不带环单链表相交,那么他们的尾指针一定相同。只需判断他们的尾指针是否相同即可。

注:判断两个单链表是否相交不能空指针。

// 判断两个单链表是否相交(链表不带环)
int IsListCross(PNode PL1, PNode PL2)
{	//采用判断尾部是否相等来判断是否相交
	PNode pTailNode_1 = NULL;//L1尾节点
	PNode pTailNode_2 = NULL;//L2尾节点

	assert(NULL != PL1);
	assert(NULL != PL2);
	
	pTailNode_1 = Back(PL1);
	pTailNode_2 = Back(PL2);

	return (pTailNode_1 == pTailNode_2);
}

5,若不带环的单链表相交,求交点

解析:先获取两个链表长度只差,然后让长链表先走,补上这个差值。

然后,两个链表同时遍历,直到遍历完毕,或者两个链表当前节点相同,即有交点。

注:两个链表为空。

// 若不带环的单链表相交,求交点
PNode GetCrossNode(PNode PL1, PNode PL2)
{	//根据两个链表不同部分长度之差,先让较长链表走它两只差的步长
	//然后,两个链表同时开始遍历,直到两者所指向节点相同,即找到交点。

	//获得PL1和PL2长度
	size_t len_1 = 0;
	size_t len_2 = 0;

	//如果存在空链表,返回NULL
	if ((NULL == PL1) || (NULL == PL2))
		return NULL;

	//如果PL1和PL2不相交,返回NULL
	if (!IsListCross(PL1, PL2))
		return NULL;

	len_1 = Size(PL1);
	len_2 = Size(PL2);
	
	//PL1长度大于PL2
	if (len_1 > len_2)
	{	//长度之差
		size_t diff = len_1 - len_2;
		//PL1先走diff步
		while (diff--)
			PL1 = PL1->_pNext;
	}
	else	//PL2长度大于PL1
	{	//长度之差
		size_t diff = len_2 - len_1;
		//PL2先走diff步
		while (diff--)
			PL2 = PL2->_pNext;
	}	
	//当PL1和PL2指向的节点不同时
	while (PL1 != PL2)
	{
		PL1 = PL1->_pNext;
		PL2 = PL2->_pNext;
	}
	//此时PL1和PL2指向节点相同,即链表的交点
	return PL1;
}



6,判断链表是否带环,若带环,给出相遇点(不是环的入口点)

解析:定义一个快慢指针,快指针每次走一步,慢指针每次走一步。

当快指针不为尾节点时,一直遍历链表,直到快慢指针相同。即说明有环,返回相遇点pMeetNode。

注:链表为空或只有一个节点时,返回NULL。

// 判断链表是否带环,若带环给出相遇点(不是环的入口)
PNode HasCircle(PNode pHead)
{	//定义一个快指针,每次走两步
	//定义一个慢指针,每次走一步
	//当两个相交时,即证明链表有环
	PNode pFast = NULL;
	PNode pSlow = NULL;
	//快慢指针相交的节点
	PNode pMeetNode = NULL;
	
	//当链表为空或只有一个节点时,链表没有环,返回NULL
	if ((NULL == pHead) || (NULL == pHead->_pNext))
		return NULL;

	pFast = pHead;
	pSlow = pHead;
	//以pFast遍历为条件
	//需要同时判断pFast和pFast->_pNext是否为空,因为pFast一次走两步
	while ((pFast != NULL) && (NULL != pFast->_pNext))
	{
		pFast = pFast->_pNext->_pNext;
		pSlow = pSlow->_pNext;
		
		//有环
		if (pFast == pSlow)
		{
			pMeetNode = pFast;
			return pMeetNode;
		}
	}
	
	//链表不带环
	return NULL;
}



7,求环的长度

解析:利用一指针pCurNode指向相遇点,遍历环,直到pCurNode再次等于pMeetNode,计算遍历次数返回即可。

注:环的初始化为1.

相遇点为空。

// 求环的长度
size_t  GetCircleLen(PNode pMeetNode)
{//思路:遍历环,知道pCurNode==pMeetNode,即可。
	PNode pCurNode = NULL;//需要遍历的当前节点
	size_t length = 1;//环的长度初始化为1

	if (NULL == pMeetNode)
		return 0;

	pCurNode = pMeetNode;

	while (pCurNode->_pNext != pMeetNode)
	{
		++length;
		pCurNode = pCurNode->_pNext;
	}

	return length;
}



8,求环的入口点

解析:在一直带环链表的相遇点后,可以以此为基础,将该链表分为两个单链表。变成求两个单链表交点的问题

其中一个链表的头结点是pHead,尾节点是pMeetNode。

另一个链表的头结点是pMeetNode的下一节点,尾节点是pMeetNode。

注:链表为空或链表长度为1.

// 求环的入口点
PNode GetEnterNode(PNode pHead, PNode pMeetNode)
{
	PNode PL1 = NULL;
	PNode PL2 = NULL;
	size_t len_1 = 1;
	size_t len_2 = 1;
	if ((NULL == pHead) || (NULL == pHead->_pNext))
		return NULL;
	if (NULL == HasCircle(pHead))
		return NULL;
	//可以看做是两个不带环单链表相交,求交点
	//一个链表的头结点是pHead,尾节点是pMeetNode
	//一个链表的头结点是pMeetNode->_pNext,尾节点是pMeetNode	
	PL1 = pHead;
	PL2 = pMeetNode->_pNext;

	//接下来采用求得两个不带环单链表求交的方法。
	while (PL1 != pMeetNode)
	{//计算len_1
		++len_1;
		PL1 = PL1->_pNext;
	}
	while (PL2 != pMeetNode)
	{//计算len_2
		++len_2;
		PL2 = PL2->_pNext;
	}
	//两个链表头指针重新指向对应的头结点
	PL1 = pHead;
	PL2 = pMeetNode->_pNext;

	if (len_1 > len_2)
	{	//PL1先走diff步
		size_t len_diff = len_1 - len_2;
		while (len_diff--)
			PL1 = PL1->_pNext;
	}
	else
	{	//PL1先走diff步
		size_t len_diff = len_2 - len_1;
		while (len_diff--)
			PL2 = PL2->_pNext;
	}
	//现在PL1和PL2距离环的入口点距离相同
	//如果PL1和PL2不同,一直走下去
	while (PL1 != PL2)
	{
		PL1 = PL1->_pNext;
		PL2 = PL2->_pNext;
	}
	//返回交点
	return PL1;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值