单链表相交问题C++

【题目描述】

给定两个可能有环也可能无环的单链表,头节点head1和和head2。请实现一个函数,如果两个链表相交,请返回相交的第一个节点。如果不想交,返回null

【题目分析】

基于这两个链表分别可能有环和无环,基于单链表只能存在一个next指针的特性,我们分成3中情况讨论:

1)两个链表都无环

则两个链表想要相交,则一定是如下这种形态

图1

即两个链表从某个节点开始,存在公共部分,公共部分的第一个节点则为相交节点。

2)其中一个链表有环,一个链表无环

由于单链表只能有一个next节点,有环链表的环一定在尾部,而结尾不同的两个链表是无法相交的。所以可以得出结论,不存在这样情况下的相交链表。

3)两个链表都有环

则两个链表想要相交,则可能是下面这种形态:相交部分在环外

图2

或者下面这种形态:相交部分在环内,并且环的方向一定是统一的

图3

  【算法分析】

1、针对于上诉1)的情况:即两个无环节点判断相交

首先,如果这样的两个链表相交,则它们的最后一个节点一定是相同的。如果不同,则一定不想交;

其次,从图1可以看出:如果这样两个链表相交,从某一节点开始,两个剩余部分是相同的。则我们可以先遍历较长链表,等到剩余长度相同,两个链表同时遍历下去。如果两个链表相交,则一定存在某一节点是相同的;

代码如下

ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    //判空
    if(nullptr == headA || nullptr == headB)
    {
        return nullptr;
    }
	//1)两个无环链表
	int lenN = 0; //用于记录两个链表的长度差值
	//先遍历一个链表
	ListNode* temp1 = headA, *temp2 = headB;

	while (nullptr != temp1->next || nullptr != temp2->next)
	{
		//A链表遍历完成,tempA为A的最后一个节点,继续遍历B链表,得到B的最后一个节点tempB
		if (nullptr == temp1->next)
		{
			while (nullptr != temp2->next)
			{
				temp2 = temp2->next;
				--lenN;
			}
			break;
		}
		//B链表遍历完成,tempB为B的最后一个节点,继续遍历A链表,得到A的最后一个节点tempA
		else if (nullptr == temp2->next)
		{
			while (nullptr != temp1->next)
			{
				temp1 = temp1->next;
				++lenN;
			}
			break;
		}

		temp1 = temp1->next;
		temp2 = temp2->next;
	}

	//tempA和tempB不相等,则不想交
	if (temp1 != temp2)
	{
		return nullptr;
	}

	temp1 = lenN > 0 ? headA : headB; //将长链表头节点赋值temp1
	temp2 = lenN > 0 ? headB : headA; //将短链表头节点赋值temp2
	lenN = abs(lenN);

	//长链表先行lenN个节点
	while (lenN-- > 0)
	{
		temp1 = temp1->next;
	}

	//两个链表同时遍历,如果存在相等节点,则相交,否则不想交
	while (nullptr != temp1)
	{
		if (temp1 == temp2)
		{
			return temp1;
		}

		temp1 = temp1->next;
		temp2 = temp2->next;
	}

	return nullptr;
}

2、针对于3)图2的情况:两个链表相交于环外

则这两个链表的入环节点肯定是相同的,使用这一特点可以将3)图2和图3两种情况区分出来

这种情况,我们可以将两个链表的第一个入环节点作为"end"节点,将此作为两个无环链表相交问题求解

3)针对于3)图3的情况:两个链表相交于环内

两个链表的入环节点不同,分别记为loop1和loop2。则可见loop2是loop1所在环中的某一节点。则,我们可以让loop2“原地等待”,让loop1继续往下走,如果在转回loop1之前能够遇上loop2,则两个链表是相交的(loop1和loop2都可以作为第一相交节点返回),否则不想交。

下列代码实现有环链表的相交求解

ListNode *getLoopListIntersectionNode(ListNode *headA, ListNode *headB) {
	//1)获取两个链表的第一个入环节点
	ListNode* loop1 = hasCycle(headA), *loop2 = hasCycle(headB);

	//2)如果两个入环节点相同,则一定相交,并看做两个无环节点相交问题求解相交节点
	if (loop1 == loop2)
	{
		return getIntersectionNode(headA, headB, loop1);
	}

	//3)如果两个入环节点不同,则让loop2不动,loop1继续遍历
	ListNode* temp = loop1->next;
	while (temp != loop1)
	{
		if (temp == loop2)
		{
			return loop1;
		}
		temp = temp->next;
	}
	
	return nullptr;
}

完整实现请见下列【代码求解】部分

【代码求解】

getIntersectionNode函数为总入口

/*
说明  : 返回单链表的入环节点
参数  : head - 单链表的头节点
返回  : ListNode*入环节点指针,如果无环则返回nullptr
*/
ListNode* getLoopNode(ListNode* head)
{
	ListNode* fast = head;
	ListNode* slow = head;

	//判断是否有环
	while (nullptr != fast && nullptr != slow)
	{
		if (nullptr == fast->next) return nullptr;

		fast = fast->next->next;
		slow = slow->next;

		//无环,返回空指针
		if (nullptr == fast || nullptr == slow)
		{
			return nullptr;
		}

		if (fast == slow)
		{
			break;
		}
	}

	//快指针回到头结点,快慢指针每次均走一步,相遇节点为第一个入环节点
	fast = head;
	while (nullptr != slow)
	{
		if (fast == slow)
		{
			break;
		}
		fast = fast->next;
		slow = slow->next;
	}

	return slow;
}

/*
说明  : 无环链表获取相交节点求解
参数  : headA、headB - 单链表的头节点 comEnd 两个链表的共同尾结点,为两个入环节点相同的链表判断相交节点的场景设计
返回  : ListNode*入环节点指针,如果无环则返回nullptr
*/
ListNode *getNoLoopIntersectionNode(ListNode *headA, ListNode *headB, ListNode* comEnd = nullptr) {
	//1)两个无环链表
	int lenN = 0; //用于记录两个链表的长度差值
	//先遍历一个链表
	ListNode* temp1 = headA, *temp2 = headB;

	while (comEnd != temp1->next || comEnd != temp2->next)
	{
		//A链表遍历完成,tempA为A的最后一个节点,继续遍历B链表,得到B的最后一个节点tempB
		if (comEnd == temp1->next)
		{
			while (comEnd != temp2->next)
			{
				temp2 = temp2->next;
				--lenN;
			}
			break;
		}
		//B链表遍历完成,tempB为B的最后一个节点,继续遍历A链表,得到A的最后一个节点tempA
		else if (comEnd == temp2->next)
		{
			while (comEnd != temp1->next)
			{
				temp1 = temp1->next;
				++lenN;
			}
			break;
		}

		temp1 = temp1->next;
		temp2 = temp2->next;
	}

	//tempA和tempB不相等,则不想交
	if (temp1 != temp2)
	{
		return comEnd;
	}

	temp1 = lenN > 0 ? headA : headB; //将长链表头节点赋值temp1
	temp2 = lenN > 0 ? headB : headA; //将短链表头节点赋值temp2
	lenN = abs(lenN);

	//长链表先行lenN个节点
	while (lenN-- > 0)
	{
		temp1 = temp1->next;
	}

	//两个链表同时遍历,如果存在相等节点,则相交,否则不想交
	while (comEnd != temp1)
	{
		if (temp1 == temp2)
		{
			return temp1;
		}

		temp1 = temp1->next;
		temp2 = temp2->next;
	}

	return comEnd;
}

/*
说明  : 链表相交节点求解
参数  : headA、headB - 单链表的头节点
返回  : ListNode*入环节点指针,如果无环则返回nullptr
*/
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB)
{
	//判空
	if (nullptr == headA || nullptr == headB)
	{
		return nullptr;
	}
	//1)获取两个链表的第一个入环节点
	ListNode* loop1 = getLoopNode(headA), *loop2 = getLoopNode(headB);
	//如果两个入环节点相同,则是2个无环链表或2个入环节点相同的有环链表场景
	if (loop1 == loop2)
	{
		return getNoLoopIntersectionNode(headA, headB, loop1);
	}

	//两个有环链表,且入环节点不同
	ListNode* temp = loop1->next;
	while (temp != loop1)
	{
		if (temp == loop2)
		{
			return loop1;
		}
		temp = temp->next;
	}

	return nullptr;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值