单向链表相交的一系列问题

问题描述

两个单向链表,求第一个相交的点,实际这是3个问题!!!

  1. 怎么判断 1个链表是否有环 并找到环的入口
  2. 怎么找到2个无环单链表第一个相交的节点
  3. 怎么找到2个有环单链表第一个相交的节点
    tips: (假如一个有环,一个无环,两个单向链表(因为是单向链表)不可能相交)

总体代码

先判断两个链表是否有环,
假如都无环,用都无环的方法找相交节点,假如一个有环一个无环,不可能相交,假如都有环,用都有环的方法找相交节点。

class Solution {
public:
	using node = ListNode*;
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        if(!pHead1 || !pHead2 ){
			return nullptr;
		}
		
		node loop1 = getloop(pHead1);
		node loop2 = getloop(pHead2);
		if(!loop1 && !loop2)
			return noloop(pHead1,pHead2);
		else if(loop1 && loop2)
			return bothloop(pHead1,pHead2,loop1,loop2);
		else
			return nullptr;
    }

1.有无环及入口

问题1:怎么判断 1个链表是否有环 并找到环的入口
一个 慢指针p1每次走1步,一个快指针p2每次走2步,假如无环,快指针一定遇到终点也就是 next是nullptr,那直接返回nullptr,假如有环,那p1和p2肯定会在环中的某个位置相遇。这个时候把快指针 放回链表开头位置,并且每次走一步,同时慢指针也从那个相遇的位置每次走一步,最后两个指针相遇的位置一定是环的入口(证明见最后)

class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
		//假如头结点为空,只有一个节点,只有两个节点且不构成环,直接nullptr
		if(!pHead || !pHead->next || !pHead->next->next) return nullptr;
		
		//找到相遇点
		using node = ListNode;
		node *p1=pHead->next, * p2 = pHead->next->next;
		while(p1 !=p2){
			if(!p2->next || !p2->next->next) 
				return nullptr;
			p1 = p1->next;
			p2 = p2->next->next;
		}
		
		//找到环入口
		p2 = pHead;
		while(p1!=p2){
			p1=p1->next;
			p2=p2->next;
		}
		return p1;
    }
};

假设x为环前面的路程(黑色路程),a为环入口到相遇点的路程(蓝色路程,假设顺时针走), c为环的长度(蓝色+橙色路程)
当快慢指针相遇的时候:
此时慢指针走的路程为Sslow = x + m * c + a
快指针走的路程为Sfast = x + n * c + a
2 Sslow = Sfast
2 * ( x + m*c + a ) = (x + n *c + a)
从而可以推导出:
x = (n - 2 * m )*c - a
也就是环前面的路程 就是从相遇点开始,转了z个圈,再往回退a步,也就是环的入口。
所以,我们可以让快指针从起点A开始走,让慢指针从相遇点B开始继续往后走,
2个指针速度一样,那么,当从原点的指针走到环入口点的时候(此时刚好走了x)
从相遇点开始走的那个指针也一定刚好到达环入口点。
所以2者会相遇,且恰好相遇在环的入口点。

2. 2个无环相交

问题2:怎么找到2个无环单链表第一个相交的节点?
2个无环单链表,假如相交,那只能是Y型的
那么找的过程是
链表1 遍历到结尾节点end1,同时记录总结点数len1
链表2 遍历到结尾节点end2, 同时记录总结点数len2
比较end1和end2,不相等返回nullptr,相等进入下一步操作
len大的那个链表,假如是链表1,先走len1-len2步,
然后两个链表一起走,第一个两个节点相等的结果就是第一个相交的节点。

	node noloop(node root1,node root2){
		if(!root1 || !root2) return nullptr;
		
		node cur1 = root1;
		int len1 = 1;
		while(cur1->next){
			cur1 = cur1->next;
			++len1;
		}
		
		node cur2 = root2;
		int len2 = 1;
		while(cur2->next){
			cur2 = cur2->next;
			++len2;
		}
		//假如两个终点不一样,必不可能相交 不要忘记这个
		if(cur1 !=cur2) return nullptr;
		
		//相交时
		cur1 = (len1>len2?root1:root2);
		cur2 = (cur1==root1?root2:root1);
		for(int i=0;i<abs(len1-len2);++i){
			cur1=cur1->next;
		}
		while(cur1!=cur2){
			cur1=cur1->next;
			cur2=cur2->next;
		}
		return cur1;
	}

3. 2个有环相交

问题3:怎么找到2个有环单链表第一个相交的节点?
分成3种情况
1)两个环的入口相等 那么总体形状是 Y+一个环,
这种情况就跟问题2,两个无环的第一个相交节点类似,只不过现在终点是环的入口
2)两个环的入口不等,且两个链表不相交,
3)两个环的入口不等,但是两个链表相交, 那拓扑形状就是从一个环,分出两条线,这样,第一个相交点就是两个环入口的任何一个

那怎么分辨情况2和情况3呢,让链表1从loop1出发,如果回到loop1之前没有碰到loop2那就是情况2,如果碰到了那就是情况3
	node bothloop(node root1,node root2, node loop1,node loop2){
		//两个环入口相等
		if(loop1==loop2){
			
			node cur1 = root1;
			int len1 = 1;
			while(cur1 != loop1){
				cur1=cur1->next;
				++len1;
			}
			
			node cur2 = root2;
			int len2 = 1;
			while(cur2 !=loop2){
				cur2 = cur2->next;
				++len2;
			}
			
			cur1 = (len1>len2?root1:root2);
			cur2 = (cur1==root1?root2:root1);
			for(int i=0;i<abs(len1-len2);++i){
				cur1=cur1->next;
			}
			//因为已经确定 两个环的入口相等了,所以不用判断两个终点是否相等
			while(cur1 !=cur2){
				cur1=cur1->next;
				cur2=cur2->next;
			}
			
			return cur1;
			
		}
		
		//两个环入口不相等
		node cur1 = loop1->next;
		while(cur1!=loop1){
			//假如在转一圈的过程中遇见了loop2,那拓扑形状是一个圈长了两条线的样子,两个入环口,随便返回一个即可
			if(cur1==loop2)
				return loop1;
			cur1=cur1->next;
		}
		//否则 不相交
		return nullptr;
		
			
	}
	
};

全部代码

class Solution {
public:
	using node = ListNode*;
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        if(!pHead1 || !pHead2 ){
			return nullptr;
		}
		
		
		node loop1 = getloop(pHead1);
		node loop2 = getloop(pHead2);
		if(!loop1 && !loop2)
			return noloop(pHead1,pHead2);
		else if(loop1 && loop2)
			return bothloop(pHead1,pHead2,loop1,loop2);
		else
			return nullptr;
    }
	
	node getloop(node root){
		if(!root || !root->next || !root->next->next)
			return nullptr;
		node p1 = root->next,p2=root->next->next;
		while(p1!=p2){
			if(!p2->next || !p2->next->next) return nullptr;
			p1 = p1->next;
			p2 = p2->next->next;
		}
		p2 = root;
		while(p1!=p2){
			p1=p1->next;
			p2=p2->next;
		}
		return p1;
	} 
	
	node noloop(node root1,node root2){
		if(!root1 || !root2) return nullptr;
		
		node cur1 = root1;
		int len1 = 0;
		while(cur1){
			cur1 = cur1->next;
			++len1;
		}
		
		node cur2 = root2;
		int len2 = 0;
		while(cur2){
			cur2 = cur2->next;
			++len2;
		}
		//假如两个终点不一样,必不可能相交 不要忘记这个
		if(cur1 !=cur2) return nullptr;
		
		//相交时
		cur1 = (len1>len2?root1:root2);
		cur2 = (cur1==root1?root2:root1);
		for(int i=0;i<abs(len1-len2);++i){
			cur1=cur1->next;
		}
		while(cur1!=cur2){
			cur1=cur1->next;
			cur2=cur2->next;
		}
		return cur1;
	}
	
	node bothloop(node root1,node root2, node loop1,node loop2){
		//两个环入口相等
		if(loop1==loop2){
			
			node cur1 = root1;
			int len1 = 1;
			while(cur1 != loop1){
				cur1=cur1->next;
				++len1;
			}
			
			node cur2 = root2;
			int len2 = 1;
			while(cur2 !=loop2){
				cur2 = cur2->next;
				++len2;
			}
			
			cur1 = (len1>len2?root1:root2);
			cur2 = (cur1==root1?root2:root1);
			for(int i=0;i<abs(len1-len2);++i){
				cur1=cur1->next;
			}
			//因为已经确定 两个环的入口相等了,所以不用判断两个终点是否相等
			while(cur1 !=cur2){
				cur1=cur1->next;
				cur2=cur2->next;
			}
			
			return cur1;
			
		}
		
		//两个环入口不相等
		node cur1 = loop1->next;
		while(cur1!=loop1){
			//假如在转一圈的过程中遇见了loop2,那拓扑形状是一个圈长了两条线的样子,两个入环口,随便返回一个即可
			if(cur1==loop2)
				return loop1;
			cur1=cur1->next;
		}
		//否则 不相交
		return nullptr;
		
			
	}
	
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值