关联列表中的快指针和慢指针技术

147 阅读4分钟

在这篇文章中,我们解释了关联列表中的快指针和慢指针技术,这也被称为龟兔赛跑算法。它被用来通过使用两个指针来有效地解决一些问题。

目录:

  1. 链接列表中的快慢指针技术
  2. 问题1:关联列表的中间部分
  3. 问题2:检测关联列表中的循环

让我们开始学习关联列表中的快慢指针技术/龟兔赛跑算法。

链接列表中的快慢指针技术

快慢指针技术(也被称为龟兔赛跑算法)使用两个指针来确定关于定向数据结构的特征。这可以是一个数组,单链表,或一个图。它经常被应用于确定数据结构中是否有任何周期,因此也被称为Floyd的周期检测算法。

慢速指针和快速指针只是给两个指针变量的名称。唯一的区别是,慢速指针每次在链接列表中移动一个节点,而快速指针则每次在链接列表中移动两个节点。

这个概念可用于检测图中的循环、寻找链表的中间节点(更好的时间复杂性)、扁平化链表等情况。所有这些例子都使用了慢指针和快指针的概念。

让我用一个例子来解释:

想象一下乌龟和兔子在轨道上奔跑,

如果轨道像循环一样,
跑得快的兔子会追上乌龟。
假设问题是:
检查链表是否在循环中结束。
这个问题可以通过 slowptr 和 fastptr 来解决。
想象一下 slowptr(乌龟) 每次移动一个指针,fastptr(兔子) 每次移动两个指针。

为了实现这个算法,两个指针将从一个位置开始(在确定链表的周期的情况下是头节点)。然后,在每次迭代中,两个指针将以不同的速度前进。通常,慢速指针会向前移动一步,而快速指针会向前移动两步。尽管它们可以以任何速度自由移动,只要速度不同。

快速和慢速指针技术在链接列表中的实现:

public Node middleNodeOfList(Node head) {
    
    Node slow = head, fast = head;

    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        // Add Logic for problem at hand
        // ...
    }
    // Add Logic for problem at hand
    // ...
}

现在为了检测一个循环列表,两个指针迟早会在同一个节点相遇。如果它们从未相遇,那么就不存在循环。这是因为两个指针之间的距离在每次迭代后都会增加一个设定的数量。当距离变成与列表的长度相同时,它们就会相遇,因为它们是在一个循环中移动。

问题1:关联列表的中间

蛮力技术:O(N+N/2)

首先找出链接列表的长度。如果列表中有N个节点,这个操作需要O(N)时间。

然后,找出中间节点索引(length_of_list/2)。有两种情况,列表中有奇数或偶数的节点。
现在你可以很容易地将你的指针移到中间节点索引。

正如我们所知,我们不能直接访问关联列表中索引的节点,就像我们在数组中做的那样。

这就是慢指针和快指针的作用。基本上,我们的想法是以不同的速度移动这两个指针。比如说:

slow = slow + 1 => slow = slow.next (在关联列表中)
fast = fast + 2 => fast = fast.next.next (在关联列表中)

这两个指针都是从关联列表的头部开始的: 列表1(奇数的节点)。 1 -> 2 -> 3 -> 4 -> 5
列表2(偶数节点)。 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> null。 假设在列表中的偶数节点的情况下,可能有两个可能的中间节点(3和4)。我们想返回第二个,即4。

实施示例:

public Node middleNodeOfList(Node head) {
    
    Node slow = head, fast = head;
    
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }
    return slow;
}

现在,用你的两个食指,一个作为慢速指针,一个作为快速指针,在上面的列表中不断迭代,慢速=慢速.next,快速=快速.next.next。你会注意到,当快指针到达最后一个节点(如果是奇数列表)和 "空"(如果是偶数列表)时,慢指针将指向链接列表的中间。

问题2:检测关联列表中的循环

这是一个众所周知的问题,可以用慢-快指针的方法来解决。检测链接列表中的循环的想法是这样的。

你以这样的速度移动慢速和快速指针:慢速=慢速+1,快速=快速+2,每次迭代,即快速指针以两倍于慢速指针的速度向前移动。现在,在任何时候,如果慢速和快速指针相遇或指向同一个节点,我们可以说在链表中检测到了一个循环。

实施:

public boolean detectCycle(Node head) {
    if (head == null) {
    	return false;
    }

    Node slow = head, fast = head.next;
    while (slow != fast) {
    	if (fast == null || fast.next == null) {
    		return false;
    	}
    	slow = slow.next;
    	fast = fast.next.next;
    }
    return true;
}

上述方法的时间复杂度为O(N),空间复杂度为O(1)。

通过OpenGenus的这篇文章,你一定对关联列表中的快慢指针技术有了完整的了解。