每日一题:环形链表

118 阅读4分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

leetcode141、142环形链表

一、环形链表及判环

当链表中有环时,循环链表是死循环,一直去next一直有,所以链表判环很重要。

image-20220315183537783.png

那么如何判断链表有环呢:

  1. 最简单粗暴的进行一定量级的循环,若一直循环不结束可以认为是有环的。但是这种方法并不准确。
  2. 使用一个对象或数组或是set格式的缓存数据,循环链表时将每一项都存到这个缓存数据中,然后在接下来的循环中查找,如果有重复着出现在缓存数据中,表示有环。但是这样的处理时间复杂度是o(n)空间复杂度也是o(n)(对象类的数据格式)。
  3. 还有一种方法:定义两个快慢指针,如果链表有环,那么跑的快的指针一定会追上慢指针(两个指针指向的链表节点相同)。如果追上了,那就表示链表有环

二、题目示例

以141环形链表Ⅰ为例:

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

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

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

三种方法解题:

  1. 量级遍历
var hasCycle = function(head) {
    // 1. 量级遍历
    let count = 0
    while(head) {
        if (count>10000) {// 设置量级为1万
            return true
        }
        count+=1;
        head = head.next
    }
    return false
};

这种解法我们在leetcode中提交,居然是可以通过的!

image-20220315191048975.png

但是很明显这种方法并不严谨

2. 定义缓存对象/数组结构来判断是否有环

var hasCycle = function(head) {
    // 这里我们使用set结构,当然也可以使用其他类型的数据结构
    let cache = new Set()
    while(head) {
        if (cache.has(head)) {
            return true
        } else {
            cache.add(head)
        }
        head = head.next
    }
    return false
}

我们知道这种方法下空间复杂度为o(n)当链表很大时,这个缓存结构也会很大,于是我们可以用常量级0(1)的空间复杂度定义两个指针来优化这种方法⬇

3. 使用快慢两个指针进行判断

var hasCycle = function(head) {
    let fast = head, slow = head;  // 两个指针初始都指向头节点
    while(fast&&fast.next) {
        slow = slow.next;       // 慢指针每次跑一步
        fast = fast.next.next;  // 快指针每次跑两步
        if (slow===fast) {      // 如果两个指针的节点相同,表示追上了,有环
            return true
        }
    }
    return false
}

三、环形链表入口判定

我们将环形链表看成数学题目来计算:

image-20220316160637869.png

由上面的推导我们知道,快慢指针的相交点—>环的起始点的距离 = 头节点—>环的起始点的距离。对应到图示中的链表,我们即知道快慢指针的相遇点为 6节点处(1->2->3 = 6->7->3

现在我们再看142环形链表Ⅱ这道题:

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

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

不允许修改 链表。

跟141题不同的是,这道题有环需要返回环的起始节点,无环则返回null。

那么我们可以用两个快慢指针去判断是否有环,当两指针相遇时,表示有环时,且根据上述公式,相遇点和头节点同时往后next查找节点,当节点相同时,这个节点即为环的起始点。

代码实现:

var detectCycle = function(head) {
    const node = hasCycle(head)
    if (node) { // 有相遇点
        // 根据相遇点找和头节点找环的起始点
        let n1 = head, n2 = node;
        while(n1!==n2) {
            n1 = n1.next;
            n2 = n2.next;
        }
        return n1
    }
    // 无相遇点返回null
    return node
};
// 可以直接将上题的判环逻辑拿来复用,但是return的结果要稍微修改下
var hasCycle = function(head) {
    let fast = head, slow = head;  // 快慢指针判环
    while(fast&&fast.next) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow===fast) {
            return slow;  // 有环return指针的相遇点
        }
    }
    return null;  // 无环return null
}