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

128 阅读2分钟

一句话总结:
判断链表交叉就像找两个人走的路有没有汇合点——要么用「脚印记录法」(哈希表),要么用「对齐起点法」(双指针)!


一、链表交叉示例

链表A13579 → null  
                 ↗  
链表B24  

关键点:  交叉后的节点共用(如节点7、9)


二、哈希表法(脚印记录法)

原理:  把链表A所有节点存进哈希表,遍历链表B时检查是否有重复节点
Kotlin 代码:

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

// 哈希表法检测交叉点
fun getIntersectionNodeHash(headA: ListNode?, headB: ListNode?): ListNode? {
    val visited = hashSetOf<ListNode>() // 存储链表A的所有节点
    var currentA = headA
    
    // 记录链表A的节点
    while (currentA != null) {
        visited.add(currentA)
        currentA = currentA.next
    }
    
    // 检查链表B是否有重复节点
    var currentB = headB
    while (currentB != null) {
        if (visited.contains(currentB)) return currentB
        currentB = currentB.next
    }
    return null
}

时间复杂度:  O(m + n)
空间复杂度:  O(m)(m为链表A长度)


三、双指针法(对齐起点法)

原理:  计算两链表长度差,让长链表先走差值步,然后两指针同步前进
Kotlin 代码:

fun getIntersectionNode(headA: ListNode?, headB: ListNode?): ListNode? {
    // 获取链表长度
    fun getLength(head: ListNode?): Int {
        var len = 0
        var curr = head
        while (curr != null) {
            len++
            curr = curr.next
        }
        return len
    }
    
    val lenA = getLength(headA)
    val lenB = getLength(headB)
    
    var currA = headA
    var currB = headB
    
    // 长链表先走差值步
    if (lenA > lenB) {
        repeat(lenA - lenB) { currA = currA?.next }
    } else {
        repeat(lenB - lenA) { currB = currB?.next }
    }
    
    // 同步前进找交叉点
    while (currA != null && currB != null) {
        if (currA == currB) return currA
        currA = currA.next
        currB = currB.next
    }
    return null
}

时间复杂度:  O(m + n)
空间复杂度:  O(1)


四、测试用例

fun main() {
    // 构造交叉链表
    val commonNode = ListNode(7).apply {
        next = ListNode(9)
    }
    val headA = ListNode(1).apply {
        next = ListNode(3).apply {
            next = ListNode(5).apply {
                next = commonNode
            }
        }
    }
    val headB = ListNode(2).apply {
        next = ListNode(4).apply {
            next = commonNode
        }
    }
    
    // 测试哈希表法
    println(getIntersectionNodeHash(headA, headB)?.value) // 输出7
    
    // 测试双指针法
    println(getIntersectionNode(headA, headB)?.value)     // 输出7
    
    // 测试无交叉情况
    val headC = ListNode(10)
    println(getIntersectionNode(headA, headC))            // 输出null
    
    // 测试其中一个链表为空
    println(getIntersectionNode(null, headB))             // 输出null
}

五、关键点解析

  1. 节点相等判断:比较节点引用(===),不是比较值
  2. 处理空链表:任一链表为空直接返回null
  3. 交叉点可能在头节点:如链表B直接指向链表A的头节点

口诀:
链表交叉不用愁,
哈希双指针任你投。
对齐起点或浪漫走,
抓住共同节点就收工!