假设您在Java中有一个链表结构。它由节点组成:
class Node {
Node next;
// some user data
}
每个节点都指向下一个节点,除了最后一个节点,它的next为空。假设有一种可能性,列表可以包含一个循环-即最后的节点,而不是有一个空值,有一个引用到列表中它之前的一个节点。
最好的写作方式是什么
boolean hasLoop(Node first)
如果给定的节点是带有循环的列表的第一个,则返回true,否则返回false ?你怎么能写出一个常数的空间和合理的时间呢?
下面是一个带有循环的列表的图片:
如果链表结构实现了java.util.List. list。我们可以使用列表大小来跟踪我们在列表中的位置。
我们可以遍历节点,将当前节点的位置与上一个节点的位置进行比较。如果我们当前的位置超过了上一个位置,我们就检测到列表在某个地方有一个循环。
这种解决方案需要恒定的空间,但随着列表大小的增加,完成所需的时间会线性增加。
class LinkedList implements List {
Node first;
int listSize;
@Override
int size() {
return listSize;
}
[..]
boolean hasLoop() {
int lastPosition = size();
int currentPosition = 1;
Node next = first;
while(next != null) {
if (currentPosition > lastPosition) return true;
next = next.next;
currentPosition++;
}
return false;
}
}
或作为一种实用工具:
static boolean hasLoop(int size, Node first) {
int lastPosition = size;
int currentPosition = 1;
Node next = first;
while(next != null) {
if (currentPosition > lastPosition) return true;
next = next.next;
currentPosition++;
}
return false;
}
比弗洛伊德的算法好
Richard Brent描述了一种替代周期检测算法,它很像兔子和乌龟(弗洛伊德周期),除了这里的慢节点不移动,但随后会以固定的间隔“传送”到快节点的位置。
该描述可在布伦特的周期检测算法(瞬移海龟)。布伦特声称他的算法比弗洛伊德的循环算法快24%到36%。
O(n)时间复杂度,O(1)空间复杂度。
public static boolean hasLoop(Node root) {
if (root == null) return false;
Node slow = root, fast = root;
int taken = 0, limit = 2;
while (fast.next != null) {
fast = fast.next;
taken++;
if (slow == fast) return true;
if (taken == limit) {
taken = 0;
limit <<= 1; // equivalent to limit *= 2;
slow = fast; // teleporting the turtle (to the hare's position)
}
}
return false;
}