一句话说透数据结构里面的如何判断链表成环

176 阅读2分钟

一句话总结:
判断链表是否成环就像追人游戏——要么用“记脚印法”(哈希表),要么用“龟兔赛跑法”(快慢指针)!


一、哈希表法(记脚印法)

原理:  每走一步就在本子上记下位置,如果发现重复点就是有环!

data class ListNode(val value: Int, var next: ListNode? = null)

// 哈希表法检测环
fun hasCycleHash(head: ListNode?): Boolean {
    val visited = mutableSetOf<ListNode>() // 小本本记下所有走过的节点
    var current = head
    
    while (current != null) {
        if (current in visited) { // 发现重复脚印!
            return true
        }
        visited.add(current)      // 记录新位置
        current = current.next    // 继续前进
    }
    return false // 走到头没发现环
}

举个栗子 🌰:
链表:1 → 2 → 3 → 2(指向之前的2,形成环)
遍历到第二个2时发现已存在 → 检测到环

特点:

  • 时间复杂度:O(n)(每个节点遍历一次)
  • 空间复杂度:O(n)(需要存所有节点)

二、快慢指针法(龟兔赛跑法)

原理:  让快指针(兔子)每次跑两步,慢指针(乌龟)跑一步,如果存在环,兔子最终会追上乌龟!

Kotlin 代码:

fun hasCycleFloyd(head: ListNode?): Boolean {
    var slow = head  // 乌龟
    var fast = head  // 兔子
    
    while (fast?.next != null) { // 兔子还能跳两步
        slow = slow?.next        // 乌龟走一步
        fast = fast.next?.next   // 兔子走两步
        
        if (slow == fast) {      // 龟兔相遇!
            return true
        }
    }
    return false // 兔子跑到了终点,没环
}

举个栗子 🌰:
环形链表:1 → 2 → 3 → 1
龟兔同时从1出发:

  • 第1轮:龟到2,兔到3
  • 第2轮:龟到3,兔到2(绕环一圈)
  • 第3轮:龟到1,兔到1 → 相遇!

三、两种方法对比

方法时间复杂度空间复杂度适用场景
哈希表法O(n)O(n)快速实现,小规模数据
快慢指针法O(n)O(1)内存敏感,大规模数据

四、常见问题解答

Q1:为什么快指针走两步,不能走三步吗?
A:走两步保证一定能相遇(数学可证明)。走三步可能错过,导致死循环。

Q2:如果链表很长,快指针会不会永远追不上慢指针?
A:不会!假设环长L,快慢指针进入环后,快指针每次追近1步(2-1=1),最多追L次必相遇。

Q3:如何处理空链表或单节点链表?
A:代码中的空检查已经覆盖:

  • 空链表 → 直接返回false
  • 单节点自环 → fast?.next 不为null,进入循环后检测到相遇

五、测试用例

fun main() {
    // 测试1:无环链表 1 → 2 → 3
    val node3 = ListNode(3)
    val node2 = ListNode(2, node3)
    val node1 = ListNode(1, node2)
    println(hasCycleFloyd(node1)) // false

    // 测试2:有环链表 1 → 2 → 3 → 2
    node3.next = node2 // 形成环
    println(hasCycleFloyd(node1)) // true

    // 测试3:单节点自环
    val soloNode = ListNode(1)
    soloNode.next = soloNode
    println(hasCycleFloyd(soloNode)) // true
}

六、终极口诀

链表成环不用愁,
龟兔赛跑解你忧。
快慢指针效率高,
哈希表法也可靠! 环若存在必相遇,
无环终究走到头!