今天和同事聊天,聊到一个过往的面试题,如何判断一个单链表是否存在环,若存在输出起始节点?
最容易想到的是如果存在环那么遍历链表将会是死循环,程序无法正常退出,那么可以在遍历的时候把遍历过的节点放入hashset,每次检查新节点是否在set中出现过,若出现过则说明存在环。此方法看起来是能解决问题,但是存在比较大的问题 1:空间复杂度过大 2: 比较低效。
不妨假想一下,如果存在环,那我们用两个不同速率的指针从头开始走,这两个指针一定会相遇;如果快指针的next为null则说明不存在环,思路很简单,验证代码如下
Node类定义
1 class Node{ 2
3 int val; 4 Node next; 5
6 public Node(int val){ 7 this.val = val; 8 } 9
10 public String toString(){ 11 return val+""; 12 } 13
14 }
判断逻辑
1 public static boolean isHaveRing(Node node ){ 2 if(node == null || node.next == null){ 3 return false; 4 } 5
6 Node p =node; 7 Node q = node; 8
9 while( q.next != null){ 10 p = p.next; 11 q = q.next.next; 12 if(p.equals(q)){ 13 return true; 14 } 15 } 16 return false; 17 } 18
19
至此,可以轻松判断一个链表是否存在环。 延伸一下那么环的起点在哪呢,这个问题乍看比较复杂,那么先画个图分析一下
n为链表长度,P为快慢指针相遇的节点,C为环的起点;头节点到C点的距离为X, C节点到P节点距离为Y
下面仔细想一下当上文的慢指针和快指针相遇时发生了什么,慢指针总共走了X + Y = Z步; 而快指针走了 X+Y + K*L = 2*Z步;L为环的周长,K表示若干圈
通过上面两个等式可以得到 X+Y = K*L; 发现了什么没有?X+Y是慢指针到相遇的节点的步数,K*L是环周长的倍数(K为整数)如果此时一个指针Pointer从头部重新移动每次移动一步,另外一个指针从P节点移动每次也是移动一步,那么这两个节点一定会在P点相遇。而在这一段美妙旅程当中两个指针移动节奏一致,那么他们相伴而行的旅程应该是从C节点一直到P节点这一整段公共区域,也就是说当pointer一进入环他们就相遇了。从而可知,他们当他们首次相遇的节点即是环的起点。
有了以上理论指导,写出代码顺利成章了
1 public class TestCircle { 2
3 public static boolean isHaveRing(Node node) { 4 if (node == null || node.next == null) { 5 return false; 6 } 7
8 Node p = node; 9 Node q = node; 10
11 while (q.next != null) { 12 p = p.next; 13 q = q.next.next; 14 if (p.equals(q)) { 15 return true; 16 } 17 } 18
19 return false; 20 } 21
22 /**
23 * 获取首次相遇时,P指针的移动步数 24 * 25 * @param node 26 * @return
27 */
28 public static int getStep(Node node) { 29 if (node == null || node.next == null) { 30 return -1; 31 } 32 Node p = node; 33 Node q = node; 34 int pStep = 0; 35
36 while (q.next != null) { 37 p = p.next; 38 q = q.next.next; 39 pStep++; 40 if (p.equals(q)) { 41 return pStep; 42 } 43 } 44 return -1; 45 } 46
47 public static int getCross(Node node, int n) { 48
49 Node p = node; 50 Node q = node; 51 int i = 0; 52 while (p.next != null) { 53 if (n > i++) { 54 p = p.next; 55 continue; 56 } 57 p = p.next; 58 q = q.next; 59
60 if (p.equals(q)) { 61 return p.val; 62 } 63 } 64
65 return -1; 66 } 67
68 public static void main(String[] args) { 69 Node n1 = new Node(1); 70 Node n2 = new Node(2); 71 Node n3 = new Node(3); 72 Node n4 = new Node(4); 73 Node n5 = new Node(5); 74
75 n1.next = n2; 76 n2.next = n3; 77 n3.next = n4; 78 n4.next = n5; 79 n5.next = n2; 80 // 慢指针走的步数
81 int n = getStep(n1); 82
83 System.out.println(n); 84
85 int val = getCross(n1, n); 86
87 System.out.println(val); 88 } 89
90 }