04 · 链表

来源: labuladong 链表框架 + 代码随想录链表专题
核心价值: 掌握指针操纵——链表题的灵魂就是”改指针”
题量: 14 题(Hot 100 中题量最大的数据结构)


一、本质理解

labuladong 将链表的本质定义为 离散存储的线性结构,与之相对的是数组的连续存储。

对比数组链表
存储方式连续内存离散内存(通过指针链接)
随机访问O(1)O(n)
插入/删除O(n)O(1)(已知位置时)
额外空间O(n) 的指针存储

核心思维: 链表题的每一步操作都是在 改指针,画图是关键!


二、链表专题概览

链表基础(Day 4)
├── 虚拟头节点(dummy):统一头节点处理
├── 快慢指针:判环、找中点、找倒数第 k 个
├── 链表翻转:pre, cur, nxt 三指针
├── 合并有序链表:虚拟头 + 双指针
└── 相交链表:浪漫双指针法

链表进阶(Day 5)
├── 递归翻转:K 个一组翻转链表
├── 分治合并:归并排序思想
├── 复制带随机指针:三步法
└── LRU 缓存:哈希表 + 双向链表

三、核心模板

模板 1:虚拟头节点(统一边界)

dummy = ListNode(0, head)
# 操作链表时始终用 dummy.next 取头节点
return dummy.next

模板 2:链表翻转(三指针法)

def reverse_list(head):
    pre = None
    cur = head
    while cur:
        nxt = cur.next   # 保存下一个节点
        cur.next = pre   # 反转指针
        pre = cur        # pre 前移
        cur = nxt        # cur 前移
    return pre  # 新头节点

模板 3:合并有序链表

def merge_two_lists(l1, l2):
    dummy = ListNode(0)
    cur = dummy
    while l1 and l2:
        if l1.val <= l2.val:
            cur.next = l1
            l1 = l1.next
        else:
            cur.next = l2
            l2 = l2.next
        cur = cur.next
    cur.next = l1 if l1 else l2
    return dummy.next

模板 4:找倒数第 K 个节点(快慢指针)

def remove_nth_from_end(head, n):
    dummy = ListNode(0, head)
    fast = slow = dummy
    # fast 先走 n+1 步
    for _ in range(n + 1):
        fast = fast.next
    # fast 到尾时,slow 指向待删节点的前一个
    while fast:
        fast = fast.next
        slow = slow.next
    slow.next = slow.next.next
    return dummy.next

模板 5:K 个一组翻转链表

def reverse_k_group(head, k):
    dummy = ListNode(0, head)
    pre = dummy
    
    while True:
        # 检查是否有 k 个节点
        cur = pre
        for _ in range(k):
            cur = cur.next
            if not cur:
                return dummy.next
        
        # 翻转 k 个节点
        group_head = pre.next
        next_group = cur.next
        pre.next = reverse_sublist(group_head, k)
        group_head.next = next_group
        pre = group_head
 
def reverse_sublist(head, k):
    pre = None
    cur = head
    for _ in range(k):
        nxt = cur.next
        cur.next = pre
        pre = cur
        cur = nxt
    return pre

四、LRU 缓存(必考!哈希表 + 双向链表)

class DLinkedNode:
    def __init__(self, key=0, value=0):
        self.key = key
        self.value = value
        self.prev = None
        self.next = None
 
class LRUCache:
    def __init__(self, capacity):
        self.capacity = capacity
        self.cache = {}  # key -> node
        # 虚拟头尾节点
        self.head = DLinkedNode()
        self.tail = DLinkedNode()
        self.head.next = self.tail
        self.tail.prev = self.head
    
    def get(self, key):
        if key not in self.cache:
            return -1
        node = self.cache[key]
        self._move_to_head(node)
        return node.value
    
    def put(self, key, value):
        if key in self.cache:
            node = self.cache[key]
            node.value = value
            self._move_to_head(node)
        else:
            node = DLinkedNode(key, value)
            self.cache[key] = node
            self._add_to_head(node)
            if len(self.cache) > self.capacity:
                removed = self._remove_tail()
                del self.cache[removed.key]
    
    def _add_to_head(self, node):
        node.prev = self.head
        node.next = self.head.next
        self.head.next.prev = node
        self.head.next = node
    
    def _remove_node(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev
    
    def _move_to_head(self, node):
        self._remove_node(node)
        self._add_to_head(node)
    
    def _remove_tail(self):
        node = self.tail.prev
        self._remove_node(node)
        return node

五、Hot 100 链表题目清单

Day 4:链表基础

题号题目难度核心技巧建议用时
160相交链表Easy双指针浪漫法25 min
206反转链表Easy三指针翻转20 min
234回文链表Easy找中点 + 翻转后半30 min
141环形链表Easy快慢指针判环20 min
142环形链表 IIMediumFloyd 找环入口35 min
21合并两个有序链表Easy虚拟头 + 双指针25 min
2两数相加Medium模拟竖式加法30 min
19删除倒数第 N 个Medium快慢指针 + 虚拟头30 min

Day 5:链表进阶

题号题目难度核心技巧建议用时
24两两交换节点Medium虚拟头 + 画图30 min
25K 个一组翻转Hard分组翻转 + 组间重连45 min
138随机链表复制Medium插入克隆 + 拆分35 min
148排序链表Medium归并排序(找中点+合并)40 min
23合并 K 个链表Hard分治/最小堆40 min
146LRU 缓存Medium哈希表+双向链表45 min

六、易错点与技巧

  1. 画图!画图!画图! 链表操作必须画图,否则很容易指针乱指
  2. 虚拟头节点:90% 的链表题可以用 dummy 简化边界处理
  3. 空指针检查:始终检查 nodenode.next 是否为 None
  4. 断链与重连:修改指针前先保存下一节点 nxt = cur.next
  5. 快慢指针初始化:判环时 slow = fast = head;找中点时也相同

七、复杂度总结

操作时间复杂度空间复杂度
翻转链表O(n)O(1) 或 O(n) 递归栈
快慢指针O(n)O(1)
合并有序链表O(n)O(1)
LRU 操作O(1)O(capacity)

八、参考与延伸