问题描述:有一个单链表,其中可能有一个环,也就是某个节点的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