简单暴力解法

  1. /** 标记法 **/
  2. // 给每个已遍历过的节点加标志位,遍历链表,当出现下一个节点已被标志时,则证明单链表有环
  3. // 时间复杂度:O(n) 空间复杂度:O(n)
  4. var hasCycle = function(head) {
  5. while(head) {
  6. if(head.flag) return true
  7. head.flag = true
  8. head = head.next
  9. }
  10. return false
  11. };
  12. /** JSON.stringify **/
  13. // 利用 JSON.stringify() 不能序列化含有循环引用的结构
  14. // 时间复杂度:O(n) 空间复杂度:O(n)
  15. var hasCycle = function(head) {
  16. try{
  17. JSON.stringify(head);
  18. return false;
  19. }
  20. catch(err){
  21. return true;
  22. }
  23. };

最优解

首先创建两个指针 p1 和 p2(在 Java 里就是两个对象引用),让它们同时指向这个链表的头节点。然后开始一个大循环,在循环体中,让指针 p1 每次向后移动 1 个节点,让指针 p2 每次向后移动 2 个节点,然后比较两个指针指向的节点是否相同。如果相同,则可以判断出链表有环,如果不同,则继续下一次循环。

学过小学奥数的读者,一定听说过数学上的追及问题。此方法就类似于一个追及问题。

在一个环形跑道上,两个运动员从同一地点起跑,一个运动员速度快,另一个运动员速度慢。当两人跑了一段时间后,速度快的运动员必然会再次追上并超过速度慢的运动员,原因很简单,因为跑道是环形的。

假设链表的节点数量为 n,则该算法的时间复杂度为 O(n)。除两个指针外,没有使用任何额外的存储空间,所以空间复杂度是 O(1)。

  1. /** 快慢双指针 **/
  2. var hasCycle = function(head) {
  3. let p1 = head;
  4. let p2 = head;
  5. while (p2 != null && p2.next != null){
  6. p1 = p1.next;
  7. p2 = p2.next.next;
  8. if(p1 == p2){
  9. return true;
  10. }
  11. }
  12. return false;
  13. };

如果有环,求环长度

  1. var cycleLength = function(head) {
  2. let p1 = head;
  3. let p2 = head;
  4. let meetCount = 0;
  5. while (p2 != null && p2.next != null){
  6. p1 = p1.next;
  7. p2 = p2.next.next;
  8. if(p1 == p2){
  9. let length = 0;
  10. while (p2 != null && p2.next != null){
  11. p1 = p1.next;
  12. p2 = p2.next.next;
  13. length++;
  14. if(p1 == p2){
  15. return length;
  16. }
  17. }
  18. }
  19. }
  20. return false;
  21. };