[编程之美] PSet3.6 编程判断两个链表是否相交

问题描述:

       给出俩个单向链表的头指针,比如h1,h2,判断这俩个链表是否相交。为了简化问题,我们假设俩个链表均不带环。

图片来自:http://nanlouchen.blog.163.com/blog/static/20647809020124292137981/

解法一:暴力遍历

       直接暴力遍历,判断对于第一个链表中的每个节点是否在第二个链表中,复杂度为O(Len(h1))*O(Len(h2))。非常耗时间

解法二:Hash表法

       对第一个链表的节点进行hash排序,建立hash表,然后针对第二个链表的每个节点地址查询hash表,如果在hash表中出现,说明第二个链表和第一个链表有共同节点。时间复杂度为O(Len(h1)+Len(h2)),附加存储空间O(Length(h1))。本解法就是解法一的变形,只不过用空间换时间而已。

解法三:人工加环

       在五环的情况下,将第二个链表接在第一个链表后面,如果出现环,则说明两个链表相交。如果出现环,则从第二个链表头部遍历一定能回到起始点,时间复杂度O(Len(h2)),缺点是不容易找到他们的交点。

解法四:判断尾节点是否相同

       在无环的情况下,如果两个链表有结点相同,那么它们下一结点也相同,如此可推出只要相交尾结点一定相同。那么只要判断两链表的尾结点是否相同。时间复杂度O(Len(h1)+Len(h2))。如果判断出相交后,假设第一个链表Len1>Len2,则将第一个链表的指针右移Len1-Len2,然后循环查找是否有两指针相等的情况(已知尾节点相交向前逆推,如果是双向链表就可以直接向前逆推了,不用这么麻烦。)

扩展问题:

       1.如果我们须要求出两个链表相交的第一个节点呢? 

//判断俩链表是否相交,并求出相交的第一个节点
//方法:判断Tail是否相等,如果相等则将长链表指针起始值设定为锻炼表头部对应位置,进行遍历查询
struct Node{
	int data;
	Node *next;
};
Node* findIntersectNode(Node* HeadA , Node* HeadB)
{
	Node* p = HeadA;
	Node* q = HeadB;
	int LenA,LenB;
	LenA = LenB = 0;
	//---统计两链表长度
	while(NULL != p->next){//跳出循环时p指向TailA
		p = p->next;
		LenA++;
	}
	while(NULL != q->next){//跳出循环时q指向TailB
		q = q->next;
		LenB++;
	}
	//---判断尾节点是否相等
	if(p != q)
		return NULL;//不相交
	//---计算偏移量
	int dis = abs(LenA-LenB);
	if(LenA > LenB){
		p = HeadA;
		q = HeadB;
	}else{
		p = HeadB;
		q = HeadA;
	}
	//对较长链表进行偏移,并循环查找
	while (dis--)
		p = p->next;
	while(p != q){
		p = p->next;
		q = q->next;
	}
	return p;
}
        2.如果链表上有环呢?上面的方法须要怎么调整?

       这个问题得划分为好几个子问题,首先,如何判断链表上有环呢?如何知道环的入口点呢?如何知道环上和非环上有多少个节点呢?如何求出两个环相交的第一个交点呢?

       问题一:如何判断链表上是否有环?

       只需要设定两个指针,fast与slow,一个一次走两步,一个一次走一步,如果有环,则fast指针一定会与slow指针相遇,这是因为二者距离在不断的减小,每过一个单位时间,二者距离缩小一个单位,距离差如4,3,2,1,0。到0的时候就重合了。同样的可以看出如果fast指针一次走三步,二者距离一次减小两个单位。此时并不能加快检测速度反而有可能判别不出环。代码如下:

bool isCircle(Node* Head)
{
	Node *fast,*slow;
	fast = slow = Head;
	while(fast && fast->next){//fast->next保证fast可以接着一次走两步
		slow = slow->next;
		fast = fast->next->next;
		if(slow == fast)//fast一定比slow快,如果fast->next不为NULL则slow->next肯定不为NULL
			return true;
	}
	return false;
}
       问题二:如何知道环的入口点呢?

       

       图片来自:http://blog.sina.com.cn/s/blog_725dd1010100tqwp.html

       如图所示,fast指针所走轨迹为上面那根线所示,slow指针所走轨迹为下面那根线所示。假设此时slow走了S步,则fast走了2S步,此时fast必定在环中跑了n圈(n>=1),假设环长度为r=x+y。故有:

            s = a + x;
          2s = a + x + nr ;
      =>a + x = nr;
      =>a = (n-1)r + y;

       即从链表头到环入口点等于(n-1)次环内循环+相遇点到环入口点。于是可以从链表头和相遇点分别设一个 指针,每次各走一步,无论指针走了多少圈,两个指针必定相遇,且相遇第一点为环入口点。代码如下:

Node* findCircleIn(Node* Head)
{
	Node *fast,*slow;
	fast = slow = Head;
	while(fast && fast->next){
		slow = slow->next;
		fast = fast->next->next;
		if(slow == fast)
			break;
	}
	if(fast==NULL || fast->next==NULL)
		return NULL;//没有环
	//此时fast与slow指向相遇点
	slow = Head;
	while(slow != fast){//一定会相遇
		slow = slow->next;
		fast = fast->next;
	}
	return slow;//返回第一次相遇点
}
         问题三:如何计算环上的节点数和非环节点数?

         答:非环节点数可以统计问题二中对第一次在环入口相遇前经过的节点数得到,环上节点数可以通过记录问题一得到的相遇点,然后再次循环到达此点得到。也就是整个问题可以放在问题二中一次解决。代码如下:

Node* findCircleIn(Node* Head , int &nonLoopLen , int &loopLen)
{
	nonLoopLen = 0;
	loopLen = 0;
	Node *fast,*slow;
	fast = slow = Head;
	Node *record;
	while(fast && fast->next){
		slow = slow->next;
		fast = fast->next->next;
		if(slow == fast)
			break;
	}
	if(fast==NULL || fast->next==NULL)
		return NULL;//没有环
	//此时fast与slow指向相遇点
	record = fast;
	slow = Head;
	//---统计环上节点个数
	do{
		fast = fast->next;
		loopLen++;
	}while(fast != record);
	//---统计非环节点个数(将头指针也当做非环节点统计进去了)
	while(slow != fast){//一定会相遇
		slow = slow->next;
		fast = fast->next;
		nonLoopLen++;
	}
	return slow;//返回第一次相遇点
}

           问题四:如果两个链表都存在环,只有在两个链表的环为同一个时才能说是相交,否则不相交。假定链表1环入口点为a1,链表2环入口点为a2。则有下列示意图,图片来自:http://blog.csdn.net/yff1030/article/details/8587217,第一种情况,不相交。第二种情况,需要从头开始遍历,对于长链表节点先出发前进(Max(L1,L2)-Min(L1,L2))步,之后两个链表同时前进,每次一步,相遇的第一点即为两个链表相交的第一个点。第三种情况,对于链表1来说a1是它的相交首节点,对于链表2来说a2是它的相交首节点。


代码如下:

struct Node{
	int data;
	Node *next;
};
void traverseList(Node* &Head)
{
	Node *temp = Head;
	while(temp){
		cout<<temp->data<<" ";
		temp = temp->next;
	}
}
Node* createList(Node* Head , int data[] , int dataLen)//返回头结点
{
	if(Head != NULL)//一开始头结点须为空
		return NULL;
	for(int i=dataLen-1 ; i>=0 ; i--){//头插法,注意元素逆序
		Node *temp = new Node;
		temp->data = data[i];
		temp->next = Head;
		Head = temp;
	}
	return Head;
}
//找到两个链表相交的第一个交点(可能有环)
Node* isCircle(Node* Head)//如果有环,则返回环入口点
{
	Node* fast,* slow;
	fast = slow = Head;
	while(fast && fast->next)
	{
		slow = slow->next;
		fast = fast->next->next;
		if(fast == slow)
			return fast;
	}
	return NULL;
}
Node* findCircleIn(Node* HeadA ,Node* HeadB)
{
	Node *ListAIn = isCircle(HeadA);
	Node *ListBIN = isCircle(HeadB);
	//一个链表有环,另外一个没有环,则肯定不相交
	if(ListAIn&&!ListBIN || !ListAIn&&ListBIN)
		return NULL;

	Node *nodeA , *nodeB;
	int LenA,LenB;
	LenA = LenB = 0;
	//两个链表都没有环,从尾部递推判断相交点
	if(!ListAIn && !ListBIN)
	{
		nodeA = HeadA ; 
		nodeB = HeadB;
		while(nodeA){
			LenA++;
			nodeA = nodeA->next;
		}
		while(nodeB){
			LenB++;
			nodeB = nodeB->next;
		}
		if(nodeA != nodeB)//如果尾部节点不同,则肯定不相交
			return NULL;
		//偏移较长节点abs(LenA-LenB)
		int diff;
		nodeA = HeadA;
		nodeB = HeadB;
		if(LenA > LenB){
			diff = LenA-LenB;
			while(diff--){nodeA = nodeA->next;}
		}else{
			diff = LenB-LenA;
			while(diff--){nodeB = nodeB->next;}
		}
		while(nodeA && nodeB){//偏移后同时移动,找到第一个相同的指针
			nodeA = nodeA->next;
			nodeB = nodeB->next;
		}
		return nodeA;
	}

	//如果两个链表都有环,则看是否是相同的环,如果不是则肯定不相交
	if(ListAIn && ListBIN)
	{
		if(ListAIn != ListBIN){//如果两链表入口不等,判断是否在一个环中
			Node *temp = ListAIn->next;
			while(temp != ListAIn){//循环一周
				if(temp == ListBIN)//在同一个环中
					return ListAIn;//这儿只返回了链表1的交点
				temp = temp->next;
			}
			if(temp == ListAIn)//两链表入口不等,且不在一个环中
				return NULL;
		}
		else{//如果两链表入口相等,从两头结点处开始遍历。
			nodeA = HeadA ; 
			nodeB = HeadB;
			Node* Tail = ListAIn->next;//哨兵节点
			while(nodeA != Tail){
				LenA++;
				nodeA = nodeA->next;
			}
			while(nodeB != Tail){
				LenB++;
				nodeB = nodeB->next;
			}
			//---由于两链表均在Tail处相交,由此向前递推。
			int diff;
			nodeA = HeadA;
			nodeB = HeadB;
			if(LenA > LenB){
				diff = LenA-LenB;
				while(diff--){nodeA = nodeA->next;}
			}else{
				diff = LenB-LenA;
				while(diff--){nodeB = nodeB->next;}
			}
			//同步推进,找到入口点
			while(nodeA != nodeB){
				nodeA = nodeA->next;
				nodeB = nodeB->next;
			}
			return nodeA;
		}
	}
}
int main()
{
	int data[] = {1,2,3,4,5,6,7,8};
	Node* HeadA , *HeadB;
	Node* p ,*q ;
	HeadA = HeadB = NULL;
	HeadA = createList(HeadA,data,sizeof(data)/sizeof(int));//节点分别为1 2 3 4 5 6 7 8
	
	p = HeadA;//1节点
	q = HeadA->next->next;//3节点	
	while(p->next){
		p = p->next;
	}
	p->next = q;//8->3节点相连
	HeadB = new Node;
	HeadB->data = 9;
	//---测试用例1:
	/*
	ListA: 1 2 3 4 5 6 7 8 3 4 5·· ListB: 9 2 3 4 5···
	结果:节点2
	*/
	//HeadB->next = HeadA->next;//9与2相连
	//---测试用例2:
	/*
	ListA: 1 2 3 4 5 6 7 8 3 4 5·· ListB: 9 10 8 3 4 5···
	结果:节点3(返回的第一个链表交点)
	*/
	Node *r = new Node;
	r->data = 10;
	HeadB->next = r;//将9与10相连
	r->next = q;//10与8相连
	Node *node = findCircleIn(HeadA,HeadB);
	cout<<node->data<<endl;
	return 0;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值