编程之美链表题目总结

总结了编程之美上面关于链表的题目,有不正确的地方,欢迎拍砖,等编程之美看完了,回头刷其他题时遇到链表再补充~
目录如下(点击展开上面的目录到感兴趣的题目):

/************************************************************************/
/*                 链表经典练习题                                             
 1.从无头链表中删除节点
 2.链表逆转
 3.判断两个链表是否相交
 4.如何检查链表是否存在环
 5.求带环链表中环的长度
 6.求有环单链表的环连接点位置
 7.如果链表可能有环,判断两链表是否相交,并求相交的第一个节点*/

/************************************************************************/

1.从无头链表中删除节点

题目描述:给定一个无头指针的单链表,一个指针指向既非第一个结点也非最后一个结点,要求把该指针指向的结点删除
思路:这道题来自编程之美,在226页。起初第一次看到这道题时,觉的似乎无法删除,看了答案才恍然大悟,思路是删除该指针后面的结点,把删除后结点数据域的内容提前覆盖到该指针结点的数据域,实在巧妙,代码如下:

//1.从无头链表中删除节点
void DeleteNode(pNode p)
{
	if (NULL == p)		//保证p的合法性
	{
		return;
	}

	pNode next = p->pNext;		//要删除节点的下一个节点
	if (next != NULL)		//保证待删除节点并非最后一个节点
	{
		p->data = next->data;		//数据域内容覆盖
		p->pNext = next->pNext;		//删除next指向的节点
		delete next;
		next = NULL;
	}
}

2.链表逆转

题目描述:给定一个带头指针的单链表,要求只遍历一次,将链表元素顺序翻转过来
思路:这道题来自编程之美中无头链表中删除节点的扩展问题,页码为228。这个题目的思路跟前插法建立链表很像,用一个扫描指针遍历链表,将当前扫描指针指向的节点插入到链表第一个节点,实现过程中有一关键的指针变量,它始终指向链表的第一个节点。这样的方法时间复杂度为O(N), 空间复杂度为O(1),代码如下:

//2.链表逆转
void ReverseList(pNode pHead)
{
	//仅1个节点的链表直接返回,无需逆转
	if (NULL == pHead->pNext || NULL == pHead->pNext->pNext)
	{
		return;
	}

	pNode p = pHead->pNext;		//扫描指针
	pNode pFirst = p;		//总是指向链表中第一个有效节点
	pNode pTempFirst = p;		//记录当前第一个节点地址

	p = p->pNext;		//扫描指针直接从第2个节点开始
	while (p != NULL)
	{
		pNode tempNext = p->pNext;		//暂存当前节点所指的下一个节点地址
		pHead->pNext = p;				//下面步骤将当前节点插入到第一个节点
		p->pNext = pFirst;
		pFirst = p;		//更新pFirst
		p = tempNext;		//扫描指针移动
	}
	
	pTempFirst->pNext = NULL;		//逆转链表最后一个元素收尾
	
}


3.判断两个链表是否相交

题目描述:给两个单链表,判断它们是否相交,假设两个链表都不带环
思路:这道题来自编程之美的233页,如果两个链表相交,那么它们一定会在某个节点开始有重合,一直到末尾也是重合的,所以一个比较简单的判断方法就是将两个链表遍历到尾,比较这两个节点是否是同一个节点,这样的时间复杂度为O(|L1|+|L2|),L1,L2表示两个链表,代码如下:


//3.判断两个链表是否相交
bool IsIntersect(pNode L1, pNode L2)
{
	if (NULL == L1  || NULL == L2
		|| NULL == L1->pNext
		|| NULL == L2->pNext)			//头指针非法,或有空链表
	{
		return false;
	}

	//遍历L1,让L1指向最后一个节点
	while (L1->pNext != NULL)
	{
		L1 = L1->pNext;
	}

	//遍历L2,让L2指向最后一个节点
	while (L2->pNext != NULL)
	{
		L2 = L2->pNext;
	}

	//判断最后的节点是否为同一个
	return (L2 == L1);
}


4.如何检查链表是否存在环

题目描述:给定一个单链表,怎样检查链表是否存在环
思路:如果一个单链表存在环状,如果直接遍历无视一个无限循环,可以用两个指针,一个称之为慢指针pSlow,每次向前一步,另一个快指针pFast,可以让它一次向前两步,如下图,这样在环中,肯定是快的在追慢的,并且一定会相遇的,如果单链表没有环,最后的结果肯定是pFast = NULL了。代码如下:
《编程之美链表题目总结》


//4.如何检查链表是否存在环
bool IsCircle(pNode pHead)
{
	pNode pFast = pHead;
	pNode pSlow = pHead;

	//如果是不含环单链表,循环以pFast==null或者pFast->pNext==null退出
	//如果含环,则循环以pFast==pSlow退出
	while (pFast != NULL && pFast->pNext != NULL)
	{
		pFast = pFast->pNext->pNext;		//走两步
		pSlow = pSlow->pNext;			//走一步
		if (pSlow == pFast)
		{
			break;
		}
	}

	return (pFast == pSlow);
}


5.求带环链表中环的长度

题目描述:给定一个带环的链表,如何计算它的环长是多少
思路:这个是一个典型的追击问题,关键点当快慢指针在第一次相遇到第二次相遇时,快指针比慢指针多跑的路程就是圆环的长度,这个关键点用下面的图可以清楚的理解,快指针橙色多跑的路程减去蓝色慢指针跑的路程就是环长。代码如下:

《编程之美链表题目总结》



//5.求带环链表中环的长度 这个函数并未调用上面的带环函数,是单独写的
int GetCircleLength(pNode pHead)
{
	pNode pFast = pHead;
	pNode pSlow = pHead;	

	//循环到第一次相遇则退出
	while (pFast != NULL && pFast->pNext != NULL)
	{
		pFast = pFast->pNext->pNext;		//走两步
		pSlow = pSlow->pNext;			//走一步
		if (pSlow == pFast)		
		{
			break;		
		}
	}

	if (pSlow != pFast)		//无环
	{
		return 0;
	}

	//从第一次相遇开始
	//环长 = n2 - n1 = 2*n1 - n1 = n1,n1是慢指针走过的节点数
	int slowSteps = 0;	
	do 
	{
		pFast = pFast->pNext->pNext;		//走两步
		pSlow = pSlow->pNext;			//走一步
		slowSteps++;
	} while (pSlow != pFast);

	return slowSteps;
}

6.求有环单链表中的环起点

题目描述:给定带环的链表,求出环的起点
思路:仍然得从第一次相遇后分析,各个距离量如下图:


《编程之美链表题目总结》



当pFast和pSlow再S1第一次相遇时,pSlow走过步数为S,pFast走过步数为2S,有如下关系:

  • S = M + X        ···· ①
  • 2S = M + X + n*R (其中n ≧ 1,取整数)   ····· 

② – ①,得到: 
S = n*R      ·····③
将③带入到 ①得到:
n*R = M + X => M = n*R – X
从这个等式我们得到什么信息呢,如果从第一次相遇开始,慢指针和一个头指针分别同步开始走,那么当头指针走到S0时,此时慢指针走了n*R – X的距离,n*R表示走了多遍圆环,等同于还是在S1,减掉X后就到了S0,这个意思就是慢指针和一个头指针开始同步走,他们相遇的点一定是S0,即环的起点,得到所求,见代码:



//6.求有环单链表的环连接点位置
//这个函数并未调用上面的带环函数,是单独写的
pNode GetConnectionPoint(pNode pHead)
{
	pNode pFast = pHead;
	pNode pSlow = pHead;	
	
	//循环到第一次相遇则退出
	while (pFast != NULL && pFast->pNext != NULL)
	{
		pFast = pFast->pNext->pNext;		//走两步
		pSlow = pSlow->pNext;			//走一步
		if (pSlow == pFast)		
		{
			break;		
		}
	}
	
	if (pSlow != pFast)		//无环
	{
		return NULL;
	}

	//令pFast从头开始走
	pFast = pHead;
	while (pFast != pSlow)
	{
		pFast = pFast->pNext;
		pSlow = pSlow->pNext;
	}
	return pFast;
}

7.如果链表可能有环,判断两链表是否相交,并求相交的第一个节点

题目描述:给定两个单链表,均有可能带环,如何判断两个链表是否相交,并求得两个链表相交的第一个节点
思路:这个题只写了思路,没贴代码了。参考了网上的一些解法,先把可能的情况分为下面三种:

  1. 其中一个链表带环,一个不带环
  2. 两个链表都不含环
  3. 两个链表都带环

对于第一种情况,两个链表是不会相交的,因为如果一个链表带坏,且两个链表相交,则另一个链表一定带环。对于第二种情况,如果只求两个链表是否相交,可以直接用3题的方法,如果要求其交点,则需要另一种做法。把其中一个链表(记为L1)首尾相接,检查L2是否带环,如果带环则相交,并按照第5题求出交点。对于第三种情况,只要知道某个链表环上的一点是否在另一个链表上即可,要求的一个链表环上的一点,可以用前面快慢指针的方法,记用此方法求的L1中环上点为pos1, 这个时候可以遍历L2,如果相交,则比会存在等于pos1的节点,但如果不相交,这个遍历又会成为无限循环。可以用同样的方法求的L2中环上点为pos2, 然后让pos1在L1中遍历,在下一次到pos1之前判断是否遇到pos2, 如果遇到则相交,否则可以判断为不相交。这种情况下两个链表的交点似乎无法清楚的定义。整体题求法都得用到上面的方法,判断有无环是关键,代码的话,可以把其中几个反复用到的功能打包成函数,反复调用,其逻辑和实现思路就比较清晰了。

    原文作者:a635661820
    原文地址: https://blog.csdn.net/a635661820/article/details/44511743
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞