判断链表是否有环

  • Post author:
  • Post category:其他


判断链表是否有环的解法有很多,下面归纳几种常见算法。


判断链表是否存在环


方法一:

通过双循环遍历完成,外层循环从头结点开始遍历,对于遍历到一个新节点newNode,内层循环都要从头结点遍历到该新节点的上一个节点head。如果链表无环,则newNode和head永不相等,否则就会相等。时间复杂度为O(n^2)。

public class LinkListCycle {
    public static class Node {
        private int data;
        private Node next;

        public Node(int data) {
            this.data = data;
        }
    }

    public static void main(String[] args) {
        Node node1 = new Node(5);
        Node node2 = new Node(3);
        Node node3 = new Node(7);
        Node node4 = new Node(2);
        Node node5 = new Node(6);
        Node node6 = new Node(8);
        Node node7 = new Node(1);
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;
        node5.next = node6;
        node6.next = node7;
        node7.next = node7;
        LinkListCycle linkListCycle = new LinkListCycle();
        linkListCycle.hasCycleForMethodOne(node1);
        System.out.println(linkListCycle.getCycleLength(node1));
    }

    //判断是否存在环,双重遍历,时间复杂度为O(n^2)
    public boolean hasCycleForMethodOne(Node node) {
        //外部遍历新节点
        Node newNode = node;
        while (newNode != null) {
            //从头节点遍历到新节点的上一个节点
            Node head = node;
            while (head != null && head.next != newNode) {
                if (head == newNode) {
                    return true;
                }

                head = head.next;
            }

            newNode = newNode.next;
        }

        return false;
    }
}


方法二:

使用HashMap(或者HashSet),以Node节点作为key,遍历链表,如果node节点不存在则添加到集合中,如果节点存在则说明有环。时间复杂度为O(n),空间复杂度为O(n)。

    //使用map查找环,时间复杂度为O(n),空间复杂度为O(n)
    public boolean hasCycleForMethodTwo(Node node) {
        Map<Node, Integer> map = new HashMap<>();
        while (node != null) {
            if (!map.containsKey(node)) {
                map.put(node, 1);
            } else {
                //此时的node即为环的入口
                System.out.println(node);
                return true;
            }
            node = node.next;
        }
        return false;
    }


方法三:

快慢指针。类似于数学中的追及问题,设置一个快指针,步长为2,设置一个慢指针,步长为1。如果不存在环,则快慢指针不会相遇。如果存在环,则快慢指针会相遇。时间复杂度为O(n),空间复杂度为O(1)。


此为最优方法

    //使用快慢指针查找环,时间复杂度为O(n),空间复杂度为O(1)
    public boolean hasCycleForMethodThree(Node node) {
        //快指针,每次走2步
        Node fast = node;
        //慢指针,每次走1步
        Node low = node;

        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            low = low.next;

            //快慢指针相遇,存在环
            if (fast == low) {
                return true;
            }
        }
        return false;
    }


求环的长度


方法一:

使用HashMap,对环进行多次遍历。

    //查找环的长度
    public int getCycleLength(Node node) {
        Map<Node, Integer> map = new HashMap<>();
        int count = 0;
        while (node != null) {
            if (!map.containsKey(node)) {
                map.put(node, 1);
            } else {
                map.put(node, map.get(node) + 1);
            }
            //对环进行第二遍遍历
            if (map.get(node) == 2) {
                count++;
            }

            //对环第三遍遍历
            if (map.get(node) == 3) {
                break;
            }
            node = node.next;
        }
        return count;
    }


方法二:

以指针第一次相遇为起点,记录第二次相遇时的步数即为环的长度。

    public int getCycleLength(Node node) {
        //快指针,每次走2步
        Node fast = node;
        //满指针,每次走1步
        Node low = node;

        //指针是否相遇
        boolean first = false;
        //环的长度
        int count = 0;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            low = low.next;

            if (first) {
                count++;
            }

            //快慢指针相遇,存在环
            if (fast == low) {
                if (first) {
                    //第二次相遇
                    return count;
                } else {
                    //第一次相遇
                    first = true;
                }
            }
        }
        return -1;
    }


求环的入口


方法一:

见判断链表是否存在环#方法二。


方法二:

设D为从头节点到入环点的步长,逆时针方向,s1为入环点到首次相遇点的步长,s2为从首次相遇点到入环点的步长。在相遇点快慢指针走过的距离为:


2(D+S1 ) = D+S1 +n(S1 +S2 )–>D = (n-1)(S1 +S2 )+S2

即从链表头结点到入环点的距离,等于从首次相遇点绕环n-1圈再回到入环点的距离。这样一来,只要把其中一个指针放回到头节点位置,另一个指针保持在首次相遇点,两个指针都是每次向前走1步。那么,它们最终相遇的节点,就是入环节点。

    public Node getCycleEntrance(Node node) {
        //快指针,每次走2步
        Node fast = node;
        //满指针,每次走1步
        Node low = node;

        boolean hasCycle = false;

        //获取第一次相遇节点fast
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            low = low.next;

            //快慢指针相遇,存在环
            if (fast == low) {
                hasCycle = true;
                break;
            }
        }

        //无环直接返回
        if (!hasCycle) {
            return null;
        }

        //指向头节点
        Node head = node;
        //指向第一次相遇节点
        Node counter = fast;
        while (head != null && counter != null) {
            if (head == counter) {
                return head;
            }
            head = head.next;
            counter = counter.next;
        }

        return null;
    }



版权声明:本文为MAX_VALUE原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。