LeetCode-Go中的链表高级操作:LRU缓存与复杂链表复制

LeetCode-Go中的链表高级操作:LRU缓存与复杂链表复制

【免费下载链接】LeetCode-Go 该内容是使用Go语言编写的LeetCode题目的完整解决方案集合,实现了100%的测试覆盖率,并且运行时间优于所有题目100%的提交结果。 【免费下载链接】LeetCode-Go 项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go

在算法面试中,链表操作因其灵活性和易错性成为高频考点。本文将深入解析LeetCode-Go项目中两种典型的链表高级应用场景:LRU缓存实现与带随机指针的链表复制,通过分析GitHub_Trending/le/LeetCode-Go项目中的实战代码,掌握Go语言链表操作的精髓。

LRU缓存:双向链表与哈希表的完美结合

LRU(Least Recently Used)缓存机制是缓存淘汰策略的经典实现,要求在O(1)时间复杂度内完成数据的插入、查询和删除操作。LeetCode第146题的解决方案leetcode/0146.LRU-Cache/146. LRU Cache.go采用了双向链表+哈希表的经典组合。

数据结构设计

type LRUCache struct {
    head, tail *Node  // 双向链表的头尾指针
    Keys       map[int]*Node  // 哈希表存储key到节点的映射
    Cap        int  // 缓存容量
}

type Node struct {
    Key, Val   int
    Prev, Next *Node  // 双向链表指针
}

这种结构实现了三大核心操作:

  • Get操作:通过哈希表快速定位节点,访问后将节点移至链表头部(最近使用)
  • Put操作:插入新节点时,若缓存已满则删除尾部节点(最久未使用)
  • Add/Remove方法:维护双向链表的节点插入与删除

核心算法实现

添加节点到头部的操作展示了双向链表的精妙处理:

func (this *LRUCache) Add(node *Node) {
    node.Prev = nil
    node.Next = this.head
    if this.head != nil {
        this.head.Prev = node
    }
    this.head = node
    if this.tail == nil {
        this.tail = node
        this.tail.Next = nil
    }
}

删除节点时需处理三种情况:头节点、尾节点和中间节点:

func (this *LRUCache) Remove(node *Node) {
    if node == this.head {
        this.head = node.Next
        node.Next = nil
        return
    }
    if node == this.tail {
        this.tail = node.Prev
        node.Prev.Next = nil
        node.Prev = nil
        return
    }
    node.Prev.Next = node.Next
    node.Next.Prev = node.Prev
}

复杂链表复制:三步法实现随机指针复制

带随机指针的链表复制(LeetCode第138题)要求复制一个包含NextRandom两个指针的复杂链表。项目中的leetcode/0138.Copy-List-With-Random-Pointer/138. Copy List With Random Pointer.go采用了空间复杂度O(1)的原地复制算法。

算法步骤分解

  1. 复制节点并插入原链表:在每个原节点后插入复制节点
  2. 设置随机指针:利用原节点的Random指针确定复制节点的Random指向
  3. 拆分链表:将合并链表拆分为原链表和复制链表

核心实现代码如下:

func copyRandomList(head *Node) *Node {
    if head == nil {
        return nil
    }
    tempHead := copyNodeToLinkedList(head)  // 步骤1和2
    return splitLinkedList(tempHead)        // 步骤3
}

func copyNodeToLinkedList(head *Node) *Node {
    cur := head
    // 复制节点并插入原链表
    for cur != nil {
        node := &Node{Val: cur.Val, Next: cur.Next}
        cur.Next, cur = node, cur.Next
    }
    // 设置随机指针
    cur = head
    for cur != nil {
        if cur.Random != nil {
            cur.Next.Random = cur.Random.Next
        }
        cur = cur.Next.Next
    }
    return head
}

拆分链表时通过双指针技巧分离两个链表:

func splitLinkedList(head *Node) *Node {
    cur := head
    head = head.Next  // 复制链表的头节点
    for cur != nil && cur.Next != nil {
        cur.Next, cur = cur.Next.Next, cur.Next
    }
    return head
}

两种实现的对比分析

操作场景LRU缓存复杂链表复制
核心数据结构双向链表+哈希表单链表原地复制
时间复杂度O(1)O(n)
空间复杂度O(capacity)O(1)
难点边界节点处理随机指针映射
应用场景缓存系统、页面置换复杂数据结构深拷贝

链表操作常见陷阱

  1. 空指针处理:两种实现都充分考虑了head == nil的边界情况
  2. 指针断裂风险:LRU的Remove方法对头部、尾部和中间节点做了区分处理
  3. 循环引用:复杂链表复制中通过分步处理避免了指针混乱

项目实战价值

LeetCode-Go项目的这两个实现具有以下特点:

  • 极致性能:双向链表操作均为O(1)时间复杂度,符合题目要求
  • 代码规范:清晰的命名(如copyNodeToLinkedList)和模块化设计
  • 测试覆盖:配套的测试文件146. LRU Cache_test.go确保了代码可靠性

掌握这些链表高级操作,不仅能应对算法面试,更能在实际项目中处理如缓存系统、复杂数据结构深拷贝等场景。建议结合项目中的测试用例进行调试练习,深入理解指针操作的细节。

本文涉及的完整代码可在项目GitHub_Trending/le/LeetCode-Go中查看,更多链表相关题目可参考leetcode目录下以数字命名的题目文件夹。

【免费下载链接】LeetCode-Go 该内容是使用Go语言编写的LeetCode题目的完整解决方案集合,实现了100%的测试覆盖率,并且运行时间优于所有题目100%的提交结果。 【免费下载链接】LeetCode-Go 项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值