在一个有环的链表中找到交叉节点。
找到链表中的环节点是一个常见的问题,可以通过运行两个指针来检测。这个过程被称为龟兔赛跑算法,得名于同名寓言故事。
首先,检查链表是否为空(head为null)。如果是空的,说明没有环存在,直接停止。
否则,将第一个指针tortoise指向第一个节点head,将第二个指针hare指向第二个节点head.next。
然后,循环直到hare为null(在只有一个元素的链表中可能已经为null),每次迭代将tortoise向前移动一步,hare向前移动两步。由于hare开始时领先并且速度更快,所以它会先到达链表的末尾(如果有的话)。
如果没有末尾(即存在一个环),它们最终会指向同一个节点,此时可以停止循环,说明找到了环中的某个节点。
下面是伪代码实现:
def hasLoop(head): return false if head = null tortoise = head hare = tortoise.next while hare != null: return false if hare.next = null hare = hare.next.next tortoise = tortoise.next return true if hare = tortoise return false enddef
该算法的时间复杂度为O(n),因为访问的节点数(通过tortoise和hare)与节点数成正比。
一旦找到环中的一个节点,还有一个确定环的起点的O(n)的方法。
首先回到找到环中的某个元素的原始位置,但是不确定环的起点在哪里。
然后按照以下步骤进行:
1. 将hare向前移动,并将size设为1。
2. 只要hare和tortoise不同,就继续将hare向前移动,并每次增加size。这最终给出了环的大小,例如这种情况下是6。
3. 如果此时size为1,说明你已经在环的起点(在大小为1的环中,只有一个可能在环中的节点,所以它必须是第一个节点)。在这种情况下,直接返回hare作为起点,并跳过下面的步骤。
4. 否则,将hare和tortoise同时设置为链表的第一个元素,并将hare向前移动size次(在这种情况下是到7)。这样就得到了两个指针,它们之间的差正好是环的大小。
5. 然后,只要hare和tortoise不同,就同时将它们向前移动(hare以与tortoise相同的速度移动,我猜它已经因为第一次跑步而累了)。由于它们始终相距恰好size个元素,tortoise将会在与hare返回环的起点相同时达到环的起点。
下面是一个示例:
size tortoise hare comment ---- -------- ---- ------- 6 1 1 初始状态 7 hare前进六步 2 8 1/7不同,所以一起前进 3 3 2/8不同,所以一起前进 3/3相同,退出循环
因此,3是环的起点。由于这两个操作(检测环和发现环的起点)都是O(n)的,并且按顺序执行,整个过程的时间复杂度也是O(n)。
如果你想要对找到环的起点有一个更正式的证明,可以参考一些资源,如数学堆栈交换网站上的一个问题、维基百科上的循环检测页面,或者Peter Gammie于2016年4月17日发表的论文《龟兔算法》。
最后,有一个直观的例子可以帮助理解。想象一下模拟时钟中的时针和分针,它们以不同的速度运动,但最终会相遇。
总结起来,找到链表中的环节点和环的起点是常见的问题,可以使用龟兔赛跑算法来解决。这个算法通过两个指针在链表上移动,检测是否存在环,并找到环的起点。它的时间复杂度是O(n)。
链表中是否有循环是一个常见的问题,可以使用哈希映射或双指针方法来解决。
首先,我们来看一下使用哈希映射的方法。下面的代码使用哈希映射来判断链表是否有循环:
static bool isListHaveALoopUsingHashMap(Link *headLink) { map tempMap; Link * temp; temp = headLink; while (temp->next != NULL) { if (tempMap.find(temp) == tempMap.end()) { tempMap[temp] = 1; } else { return 0; } temp = temp->next; } return 1; }
上述代码中,我们首先定义了一个哈希映射tempMap来存储每个节点的指针。然后,我们从链表的头部开始遍历每个节点,如果当前节点在tempMap中不存在,则将其添加到tempMap中;如果当前节点已经在tempMap中存在,则说明链表有循环,返回0;最后,如果遍历结束,说明链表没有循环,返回1。
然而,上述方法的空间复杂度较高,为O(n)。因此,我们可以使用双指针方法来优化。
双指针方法的思路是使用两个指针,一个快指针和一个慢指针,从链表的头部开始,快指针每次移动两个节点,慢指针每次移动一个节点。如果链表有循环,那么快指针最终会追上慢指针,即它们会指向同一个节点;如果链表没有循环,那么快指针会先到达链表的末尾。下面是使用双指针方法来判断链表是否有循环的代码:
static bool isListHaveALoopUsingTwoPointers(Link *headLink) { Link *fastPtr, *slowPtr; fastPtr = slowPtr = headLink; while (fastPtr != NULL && fastPtr->next != NULL) { fastPtr = fastPtr->next->next; slowPtr = slowPtr->next; if (fastPtr == slowPtr) { return 1; } } return 0; }
上述代码中,我们首先定义了两个指针fastPtr和slowPtr,初始时它们都指向链表的头部。然后,我们使用一个循环来遍历链表,每次迭代中,fastPtr移动两个节点,slowPtr移动一个节点。在每次迭代中,我们都检查fastPtr和slowPtr是否指向同一个节点,如果是,则说明链表有循环,返回1;如果不是,则继续迭代。如果循环结束后还没有找到循环,则说明链表没有循环,返回0。
从上面的内容可以看出,使用哈希映射的方法可以判断链表是否有循环,但空间复杂度较高;而使用双指针方法可以在O(n)的时间复杂度下判断链表是否有循环,并且空间复杂度较低。因此,双指针方法是更好的方法。
链表中存在环的情况下,如何找到环的入口节点。
一种解决方法是使用“龟兔赛跑算法”(Tortoise and Hare algorithm)。该算法的核心思想是,使用两个指针从链表的起始位置出发,一个指针每次移动一个节点,另一个指针每次移动两个节点。如果链表中存在环,那么这两个指针最终一定会相遇。相遇点即为环的某个节点。
具体解决方法如下:
1. 初始化两个指针slow和fast,初始位置都为链表的起始节点。
2. 使用一个循环,每次将slow指针移动一个节点,fast指针移动两个节点。
3. 当slow和fast指针相遇时,证明链表中存在环。此时,将其中一个指针保持不动,另一个指针继续移动一个节点,直到两个指针再次相遇。记录下这个移动的步数P,即为环的周长。
4. 在链表起始节点处放置一个新的指针,将其移动P个节点。
5. 同时放置另一个指针在链表起始节点处,然后每次将这两个指针都向前移动一个节点,直到它们相遇的位置,即为环的入口节点。
这个方法的时间复杂度为O(n),其中n为链表的长度。相比于使用两个指针分别遍历链表和检查是否存在重复节点的O(n^2)解决方法,这个方法更加高效。
通过对“龟兔赛跑算法”的应用,我们可以找到链表中环的入口节点,解决了这个问题。这个算法的原理类似于模拟龟兔赛跑,通过不同的速度移动,最终相遇在环的某个节点处。这个算法在链表问题中有广泛的应用,是一个非常巧妙和高效的解决方法。