导航菜单

链表

链表是一种动态数据结构,通过指针连接节点。与数组不同,链表不需要连续的内存空间,这使得插入和删除操作更加高效。掌握链表的操作对于 Microsoft SDE 面试非常重要。

链表基础

链表的特点

  • 动态大小:可以动态添加和删除节点
  • 非连续内存:节点在内存中不一定连续
  • 插入删除高效:O(1) 时间插入和删除(已知位置)
  • 随机访问低效:需要 O(n) 时间访问第 k 个元素

链表的类型

1. 单链表(Singly Linked List)

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

# 创建链表
head = ListNode(1)
head.next = ListNode(2)
head.next.next = ListNode(3)

2. 双链表(Doubly Linked List)

class DoublyListNode:
    def __init__(self, val=0, prev=None, next=None):
        self.val = val
        self.prev = prev
        self.next = next

3. 循环链表(Circular Linked List)

# 循环单链表:最后一个节点的 next 指向头节点
# 循环双链表:最后一个节点的 next 指向头节点,头节点的 prev 指向最后一个节点

链表的基本操作

1. 遍历链表

def traverse(head):
    current = head
    while current:
        print(current.val)
        current = current.next

2. 查找节点

def find_node(head, target):
    current = head
    while current:
        if current.val == target:
            return current
        current = current.next
    return None

3. 插入节点

# 在头部插入
def insert_at_head(head, val):
    new_node = ListNode(val)
    new_node.next = head
    return new_node

# 在指定位置后插入
def insert_after(prev_node, val):
    if not prev_node:
        return
    new_node = ListNode(val)
    new_node.next = prev_node.next
    prev_node.next = new_node

4. 删除节点

# 删除指定值的节点
def delete_node(head, val):
    dummy = ListNode(0)
    dummy.next = head
    current = dummy
    
    while current.next:
        if current.next.val == val:
            current.next = current.next.next
        else:
            current = current.next
    
    return dummy.next

快慢指针技巧

快慢指针是处理链表问题的经典技巧,常用于:

  • 检测链表中的环
  • 找到链表的中间节点
  • 找到链表的倒数第 k 个节点
  • 判断链表是否为回文

1. 检测链表中的环

def has_cycle(head):
    if not head or not head.next:
        return False
    
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            return True
    return False

2. 找到链表的中间节点

def find_middle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
    return slow

3. 找到链表的倒数第 k 个节点

def find_kth_from_end(head, k):
    fast = slow = head
    
    # 快指针先走 k 步
    for _ in range(k):
        if not fast:
            return None
        fast = fast.next
    
    # 快慢指针同时移动
    while fast:
        slow = slow.next
        fast = fast.next
    
    return slow

常见题目

1. 反转链表

题目:反转一个单链表。

def reverse_list(head):
    prev = None
    current = head
    
    while current:
        next_temp = current.next
        current.next = prev
        prev = current
        current = next_temp
    
    return prev

2. 合并两个有序链表

题目:将两个升序链表合并为一个新的升序链表并返回。

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

3. 删除链表的倒数第 N 个节点

题目:给定一个链表,删除链表的倒数第 n 个节点,并返回链表的头结点。

def remove_nth_from_end(head, n):
    dummy = ListNode(0)
    dummy.next = head
    
    fast = slow = dummy
    
    # 快指针先走 n+1 步
    for _ in range(n + 1):
        fast = fast.next
    
    # 快慢指针同时移动
    while fast:
        slow = slow.next
        fast = fast.next
    
    # 删除节点
    slow.next = slow.next.next
    return dummy.next

4. 判断链表是否为回文

题目:判断一个链表是否为回文链表。

def is_palindrome(head):
    # 找到中间节点
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
    
    # 反转后半部分
    prev = None
    while slow:
        next_temp = slow.next
        slow.next = prev
        prev = slow
        slow = next_temp
    
    # 比较前后两部分
    while prev:
        if head.val != prev.val:
            return False
        head = head.next
        prev = prev.next
    
    return True

5. 链表相交

题目:找到两个单链表相交的起始节点。

def get_intersection_node(headA, headB):
    if not headA or not headB:
        return None
    
    pA, pB = headA, headB
    
    # 当两个指针相遇或都到达末尾时停止
    while pA != pB:
        pA = pA.next if pA else headB
        pB = pB.next if pB else headA
    
    return pA

面试技巧

1. 使用虚拟头节点

虚拟头节点(dummy node)可以简化边界情况处理:

def remove_elements(head, val):
    dummy = ListNode(0)
    dummy.next = head
    current = dummy
    
    while current.next:
        if current.next.val == val:
            current.next = current.next.next
        else:
            current = current.next
    
    return dummy.next

2. 注意边界情况

  • 空链表
  • 单个节点
  • 两个节点
  • 删除头节点或尾节点

3. 防止内存泄漏

在删除节点时,确保没有悬空指针:

# 正确做法
def delete_node(node):
    node.val = node.next.val
    node.next = node.next.next

# 注意:如果删除的是最后一个节点,需要特殊处理

总结

链表是算法面试中的重要数据结构:

  1. 掌握基本操作:遍历、查找、插入、删除
  2. 快慢指针:检测环、找中点、找倒数第 k 个节点
  3. 常见题目:反转、合并、删除、回文判断
  4. 面试技巧:使用虚拟头节点、注意边界情况

通过大量练习,你将能够熟练处理各种链表问题。


接下来,让我们学习栈与队列,这是两种重要的线性数据结构。

搜索