25.环形链表

50 阅读2分钟

题目链接

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

解法1 暴力解法

思路

暴力解法的思路依然是将链表用哈希表缓存下来。

如果当前节点存在于哈希表当中,则直接返回。反之则加入哈希表,然后继续遍历下一个节点。

当存在环时,会在哈希表当中查询到直接返回;当没有环时,当前遍历节点为 null,退出循环。

代码

function hasCycle(head: ListNode | null): boolean {
    if (!head || !head.next) {
        return false;
    }
    
    const set = new Set();
    let cur = head;
    while (cur) {
        if (set.has(cur)) {
            return true;
        } else {
            set.add(cur);
        }
        cur = cur.next;
    }

    return false;
};

时空复杂度分析

时间复杂度:需要遍历所有节点 O(n)

空间复杂度:额外哈希表空间开销 最差缓存所有节点 O(n)

解法2 快慢指针

思路

针对于空间上的优化,一般来说采用多指针的方式,而对于判断环形来说,可以采用快慢指针来判定。

如果不存在环形,那么快指针会走到 null 此时,退出循环,判定为无环。

如果存在环形,那么快慢指针会在环形内形成追击,总会在某一节点相遇。快指针一直走在慢指针前面,如果相遇了,那刚好也证明了是存在环形的,要不然不会相遇。

代码

function hasCycle(head: ListNode | null): boolean {
    if (!head || !head.next) {
        return false;
    }
    
    let fast = head;
    let slow = head;

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

        if (slow === fast) {
            return true;
        }
    }

    return false;
};

时空复杂度

时间复杂度:最坏情况是在环内追击走2倍长度,渐进复杂度为 O(n)

空间复杂度:常数变量 O(1)