单链表之判断表中是否存在环
这篇依然是学习专栏《数据结构与算法之美》的拓展练习。
链表中的环其实是一个挺有趣的问题,如果链表中存在环,对其进行遍历的时候就会发现next永远存在节点,循环往复永远不会终止。所以我们在遍历链表之前还是判断链表中是否存在环安全一点。
这篇文章不止讲解了如何判断链表中是否存在环,还贴出了链表中非环部分的长度、环的长度、环的起始点这些算法。
判断链表中是否存在环
方法一:使用快慢节点
思路
学习过其他链表算法的同学应该对快慢指针方法很熟悉了,这里也是使用快慢指针的方法。
原理就是,快指针既然走的比慢指针快,那么当链表中存在环的时候,快慢指针必定在一个位置会相遇,也就是pFast=pSlow。当这个条件一成立我们即可以断定这个链表存在环。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
public static boolean doesLinkedListHasCycle(ListNode head) { if (head == null || head.next == null) { return false; }
ListNode pFast = head.next.next,pSlow = head.next;
while (pFast.next != null && pSlow.next != null) { if (pFast == pSlow) { return true; } pFast = pFast.next.next; pSlow = pSlow.next; } return false; }
|
方法二:使用Map将节点作为key
思路
这里就是利用的Map的key唯一的特性,遍历链表,当发现map中已经存在当前遍历到的节点时,即我们是再一次来到了这个节点,证明链表存在环。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
public static boolean doesLinkedListHasCycle1(ListNode head) { if (head == null || head.next == null) { return false; } final HashMap<ListNode, Object> map = new HashMap<>(); ListNode p = head;
while (p.next != null) { if (map.containsKey(p)) { return true; } map.put(p, null); p = p.next; }
return false; }
|
查找环的起点
思路
查找环的起点的前提就是链表要存在环,所以此算法的前半部分就是利用了前面的快慢指针判断法,不同的是,当确定存在环的时候我们即跳出循环。
这里有一个推导过程,我参考别人的博客。
参考:https://blog.csdn.net/jiaobuchong/article/details/84727007
代码
看懂了上面的推导过程,我们直接按照其实现对应的代码就可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
public static ListNode findCycleHead(ListNode head) { if (head == null || head.next == null) { return null; }
ListNode pFast = head, pSlow = head; while (true) { pFast = pFast.next.next; pSlow = pSlow.next; if (pFast == null) { return null; }
if(pFast == pSlow) { break; } }
pFast = head; int i = 0; while (true) { pFast = pFast.next; pSlow = pSlow.next; i++; if (pFast == pSlow) { System.out.println("length of not cycle in this LinkedList: " + i); return pFast; } } }
|
计算环的长度
思路
这个算法依然用到了快慢指针,类比到生活中就像是两个人在操场跑步,一个人跑的快一个人跑的慢一点。
我们现在假设快指针叫快男,慢指针叫慢哥
在计算环长度这个算法中,快男和慢男同时在起点起跑,当快男第一次追上慢哥时(即快男已经领先一圈,假设这里时位置a),这是慢哥停下站在原地不动,快男继续跑,当快男再一次追上慢哥的时候(快男转了一圈回到位置a),所以刚刚快男走过的长度就是环的长度。类比到代码里应该就很好理解了。
要注意的是极端条件的处理,防止异常情况的发生,让代码尽可能的健壮
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
public static int calculateCycleLength(ListNode head) { if (head == null || head.next == null) { return 0; } ListNode pFast = head, pSlow = head; while (true) { pFast = pFast.next.next; pSlow = pSlow.next; if (pFast == null) { return 0; } if (pFast == pSlow) { break; } }
int cycleLength = 0; while (true) { pSlow = pSlow.next; cycleLength++; if (pSlow == pFast) { break; } }
return cycleLength; }
|
Last updated:
这里可以写作者留言,标签和 hexo 中所有变量及辅助函数等均可调用,示例:
http://yoursite.com/posts/11578/