【Day 6】公开打卡第6天 | 快慢指针检测链表环 + 找环入口 + 计算环长度

3 阅读3分钟
  1. 链表这块很经典了,今天把快慢指针三板斧(判环、入口、长度)全部手写一遍

  2. LeetCode 部分(对应 LeetCode 141 & 142 结合)

    • 相关题目链接:

    • 难度:简单 → 中等

    • 时间复杂度:O(n),空间 O(1)

    • 核心思路(简短总结): 快指针每次走2步,慢指针走1步。如果有环,快慢必相遇(Floyd判圈)。 相遇后:① 慢指针回起点,快指针留在相遇点,两者同时走1步,再相遇即环入口。 ② 从相遇点继续走一圈,计数即环长度。

    • 代码(Go 版本,一套完整实现):

Go

// ListNode 定义(LeetCode标准)
type ListNode struct {
    Val  int
    Next *ListNode
}

// 1. 判断是否有环(LeetCode 141)
func hasCycle(head *ListNode) bool {
    if head == nil || head.Next == nil {
        return false
    }
    slow, fast := head, head
    for fast != nil && fast.Next != nil {
        slow = slow.Next
        fast = fast.Next.Next
        if slow == fast {
            return true
        }
    }
    return false
}

// 2. 找到环的入口节点(LeetCode 142)
func detectCycle(head *ListNode) *ListNode {
    if head == nil || head.Next == nil {
        return nil
    }
    slow, fast := head, head
    // 第一阶段:找相遇点
    for fast != nil && fast.Next != nil {
        slow = slow.Next
        fast = fast.Next.Next
        if slow == fast {
            // 第二阶段:slow从头开始,fast留在相遇点,同时走1步
            slow = head
            for slow != fast {
                slow = slow.Next
                fast = fast.Next
            }
            return slow  // 相遇点即入口
        }
    }
    return nil
}

// 3. 计算环的长度(扩展题)
func cycleLength(head *ListNode) int {
    if head == nil || head.Next == nil {
        return 0
    }
    slow, fast := head, head
    for fast != nil && fast.Next != nil {
        slow = slow.Next
        fast = fast.Next.Next
        if slow == fast {
            // 从相遇点开始计数
            length := 1
            fast = fast.Next
            for slow != fast {
                fast = fast.Next
                length++
            }
            return length
        }
    }
    return 0  // 无环
}
  • C++ 版本

C++

ListNode* detectCycle(ListNode* head) {
    if (!head || !head->next) return nullptr;
    ListNode *slow = head, *fast = head;
    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast) {
            slow = head;
            while (slow != fast) {
                slow = slow->next;
                fast = fast->next;
            }
            return slow;
        }
    }
    return nullptr;
}
  • 易错点/优化点/面试追问:

    1. 快指针要检查 fast && fast->next(防止空指针)。
    2. 入口证明:相遇时慢指针走了 k 步,快走了 2k 步,多走的 k 步刚好是环的整数倍。
    3. 为什么入口相遇?从头到入口距离 = 相遇点到入口距离(数学美妙之处)。
    4. 无环返回 nil / false / 0。
    5. 扩展:如果要删除环?(找到入口后断开即可,但慎用)。
  1. 知识点部分(快慢指针通用场景)

    • 快慢指针适用场景:① 找环 ② 找中点(快走2慢走1,快到尾慢在中点) ③ 找倒数第k个(快先走k步,然后一起走)。
    • 为什么 O(1) 空间?不用哈希表记录访问节点。
    • 面试常问:为什么一定相遇?(龟兔赛跑:兔子在环里每圈追龟子1圈,最终追上)。
    • 变种:快乐数(LeetCode 202)、环形数组循环等。
  2. 今日感悟 以前背过“快慢指针找环”,但今天把判环、入口、长度三段代码全部手写 + 调试,才真正理解为什么入口处会相遇(数学证明太优雅了)。公开打卡让我不能只停在“会用”,必须搞懂原理。Day 6 了,坚持的感觉越来越强,离大厂offer又近了!

  3. 结束语 明天见! 欢迎评论区:① 你是怎么记快慢指针证明的? ② 代码哪里还能更简洁 ③ 你面试被问过链表环吗?怎么答的 #程序员打卡 #LeetCode #快慢指针 #链表环 #Go语言 #C++