单链表如果有环,则在遍历时就会出现无尽的情况。
一. 判断单链表是否有环。
两个指针,一个快,一个慢,如果有环,则会相遇。
public static boolean hasCycle(Node head) {
Node slow = head;
Node fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
return true;
}
}
return false;
}
二. 环中的第一个节点
两种方法求进入环中的第一个节点,一种是用HashMap,一种仍然用快慢指针。
1. HashMap
public static Node getCycleFirstNodeMap(Node head) {
HashMap<Node, Boolean> hashMap = new HashMap<>();
while (head != null) {
if (hashMap.get(head) != null && hashMap.get(head)) {
return head;
} else {
hashMap.put(head, true);
head = head.next;
}
}
return null;
}
2. 快慢指针
实现步骤:
- 找到第一个相遇点
- 然后将慢指针放到链表的头结点,把快指针调成与慢指针同样的速度,然后同时移动,再次相遇时的节点即为环中第一个节点。
代码如下:
public static Node getCycleFirstNode(Node head) {
Node slow = head;
Node fast = head;
while (head != null) {
if (fast == null || fast.next == null) {
return null;
}
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
slow = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return fast;
}
}
return null;
}
why?
相遇后,快指针从第一次相遇点出发,以一次一个节点的慢指针速度前进,慢指针从Head节点出发,那么只需要证明 L = k + d * n ,d为 >= 0的整数,即证明L % n = k即可。
假设 L 为进入环之前的节点数,n 为环中的节点数,m 为快慢指针第一个相遇点与环中第一个节点之间的节点数(不包括环中第一个节点,包括相遇点,m <= n,当且仅当相遇点与环中第一个节点重合时取等号),k为第一个相遇点与环中第一个节点之间的节点数(不包括第一次相遇点,包括环中第一个节点)。则在快慢指针相遇的时候有:
快指针至少经过环一周与慢指针相遇,其走过的节点数 S(快) = L + an + m ,慢指针走过的节点数 S(慢) = L + bn + m ,其中 a >= 1,b >= 0 的整数,且 a >= b + 1,且有:
S(快) = 2 * S(慢)
L + an + m = 2 * ( L + bn + m)
化简后有:
(a - 2b) * n = L + m ,其中a - 2b >= 1,
令 c = a - 2b,c >= 1 的整数,有:
c * n = L + m
因为 m = n - k ,
所以 c * n = L + n - k
L = (c - 1) * n + k
所以 L % n = k