解题笔记(30)——找含单链表的环入口点

      问题描述:有一个单链表,其中可能有一个环,也就是某个节点的next指向的是链表中在它之前的节点,这样在链表的尾部形成一环。如果链表存在环,找到环的入口点?

      思路:这道题的原型可能来自《C专家编程》一书,题目为”怎样才能检测到链表中存在循环“,书中给出的最终算法是定义两个指针p1,p2,p1每次移动一个位置,而p2每次移动两个位置,这样如果链表中存在循环,那么p2一定能追上p1。如果不存在,那么p2会到达链表尾部,即检测到空。这个比较简单,但是如果要求找出环的入口点呢?

      本文给出两种思路。第一种需要辅助空间,可以用散列表,用来存放结点的地址。定义一个指针用来遍历链表,假设指针当前指向 i 结点,检查散列表中是否含有该结点,如果有则该结点就是环的入口点。否则,将该结点的地址加入散列表中。如果链表不存在环,那么最终会到达链表末尾,从而跳出循环。实现中,由于标准库中没有hash,因此用集合代替,意思差不多。时间复杂度上会有影响,用散列表为O(n),而用集合为O(nlogn),而空间复杂度为O(n)。

      第一种方法需要O(n)的辅助空间,如果要求辅助空间为O(1),应该怎么办呢?容易想到的就是用穷举法,对于每个结点,检查该结点是否是环的入口点。可定义两个指针,p1和p2。p1每次往前移动一步,表示当前被检查的结点。p2每次从头部开始往前移动,当p1和p2相等时,检查两个指针通过的距离,如果距离一样,表示该结点不是环入口点,距离不一样,表示p1已经在环中绕了一圈,第二次到达入口点,而p2是第一次到达入口点,此时两者所指的结点即为环的入口点。这种方法的时间复杂度为O(n^2),空间复杂度为O(1)。

      是否存在时间复杂度为O(n),空间复杂度为O(1)的算法呢?等待某位网友的解答。

      参考代码:

//函数功能 : 找含单链表的环入口点
//函数参数 : pHead指向链表首部
//返回值 :   返回的是环的入口点,如果不存在环,返回NULL
ListNode* FindFirstCrossNode_Solution1(ListNode * pHead)
{
	set<ListNode *> addrSet; //这里用集合代替散列,会影响时间复杂度 
	ListNode *pNode = pHead;
	while(pNode != NULL)
	{
		if(addrSet.find(pNode) == addrSet.end())
			addrSet.insert(pNode);
		else
			break;
		pNode = pNode->next;
	}
	return pNode;
}
ListNode* FindFirstCrossNode_Solution2(ListNode * pHead)
{
	ListNode *p1 = pHead;
	int pos1 = 0;
	while(p1)   //检查链表的每个结点
	{
		ListNode *p2 = pHead;
		int pos2 = 0;
		while(p2) //每次都从头开始
		{
			if(p1 == p2) //两个指针指向的结点一样,可能是相交也可能不相交
			{
				if(pos1 == pos2) //两个指针走过的距离一样
					break;
				else  //p1在环中绕了一圈,导致pos1与pos2不一样
					return p1; 
			}
			p2 = p2->next; //前进一个位置
			pos2++;        //记录走过的距离
		}
		p1 = p1->next;
		pos1++;
	}
	return NULL;
}

       本人享有博客文章的版权,转载请标明出处 http://blog.csdn.net/wuzhekai1985

你可能感兴趣的:(解题笔记(30)——找含单链表的环入口点)