寻找两个相交链表的第一个公共节点

本文探讨了两个链表相交点的寻找方法及单向链表中环的判断和环入口确定的问题,提供了三种不同的算法解决方案及其C++实现。

Q:已知有两个链表,他们可能相交于某一点,求出该点。

方法1. 最简单的方法就是先顺序访问其中一个链表,在每访问一个节点时,都对另外一个链表进行遍历,看节点是否相等

  直到找到一个相等的节点位置,如果链表长度分别是m,n 则时间复杂度为O(mn);


方法2. 对于第一个链表,每访问一个节点,对该节点做标记。访问第二个链表,如果该元素已经访问,则第一个这样的元素就是所求点。

由于两个链表都访问了一遍,因此时间复杂度O(m+n),空间复杂度O(m)或O(n);


方法3. 如果两个单向链表有公共的结点,也就是说两个链表从某一结点开始,它们的m_pNext都指向同一个结点。但由于是单向链表的结点,每个结点只有一个m_pNext,因此从第一个公共结点开始,之后它们所有结点都是重合的,不可能再出现分叉。所以,两个有公共结点而部分重合的链表,拓扑形状看起来像一个Y,而不可能像X。

如果两个链表的总长度是相等的,那么我们对两个链表进行遍历,则一定同时到达第一个公共节点。但是链表的长度实际上不一定相同,所以我们只需要计算出两个链表的长度之差n,然后让长的那个链表先移动n步,短的链表再开始向后遍历,这样他们一定同时到达第一个公共节点,我们只需要在向后移动的时候比较两个链表的节点是否相等就可以获得第一个公共节点。时间复杂度是O(m+n)。

基于这个思路,我们不难写出如下的代码:

template<typename T>
struct ListNode
{
	T data;
	ListNode* pNext;
};
template<typename T> int GetListLength(ListNode<T>* pHead)
{
	int nLength = 0;
	while(pHead)
	{
		++nLength;
		pHead = pHead->pNext;
	}
	return nLength;
}
template<typename T>
ListNode<T>* FindFirstCommonNode(ListNode<T>* pHead1, ListNode<T>* pHead2)
{
	int nLength1 = GetListLength(pHead1);
	int nLength2 = GetListLength(pHead2);

	// 交换两链表,是1为较长的链表
	if(nLength1 < nLength2)
	{
		ListNode<T>* pTemp = pHead1;
		pHead1 = pHead2;
		pHead2 = pTemp;
	}
	for(int i = 0; i < nLength1 - nLength2; ++i)
	{
		pHead1 = pHead1->pNext;
	}
	while(pHead1 && pHead1 != pHead2)
	{
		pHead1 = pHead1->pNext;
		pHead2 = pHead2->pNext;
	}

	return pHead1;
}


上题是基于两个链表无环考虑的。

1)判断一个链表是否有环。

设置两个指针(fast, slow),初始值都指向头,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。(当然,fast先行头到尾部为NULL,则为无环链表)

// 判断链表是否有环
template<typename T>
bool HasRing(ListNode<T>* pHead)
{
	ListNode<T>* pFast = pHead;
	ListNode<T>* pSlow = pHead;
	while(pFast && pFast->pNext)
	{
		pSlow = pSlow->pNext;
		pFast = pFast->pNext->pNext;

		if(pSlow == pFast) break;
	}

	return !pFast && !pFast->pNext;
}

2)确定环入口:

当fast若与slow相遇时,slow肯定没有走遍历完链表,而fast已经在环内循环了n圈(1<=n)。

假设slow走了s步,则fast走了2s步(fast步数还等于s 加上在环上多转的n圈),设环长为r,则:

2s = s + nr
s= nr

设入口环与相遇点距离为x,x<r,起点到环入口点的距离为a.

a = s - x = (n-1)r+ (r-x), 而r-x即为fast再次到环入口点时的步数

由此可知,从链表头到环入口点等于(n-1)循环内环+相遇点到环入口点,于是我们从链表头、与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点。

// 判断链表环入口,如果没有环,返回NULL
template<typename T>
ListNode* FindRingEntrance(ListNode<T>* pHead)
{
	ListNode<T>* pFast = pHead;
	ListNode<T>* pSlow = pHead;

	// 先找到相遇点
	while(pFast && pFast->pNext)
	{
		pSlow = pSlow->pNext;
		pFast = pFast->pNext->pNext;
		if(pSlow == pFast) break;
	}

	// 一个从起点,一个从相遇点重新遍历,步长相同,当再次相遇时,就是环入口
	pSlow = pHead;
	while(pFast && pFast != pSlow)
	{
		pSlow = pSlow->pNext;
		pFast = pFast->pNext;
	}

	return pFast;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值