【笔面试算法学习专栏】链表操作专题:反转、环形检测与合并

引言

链表作为基础数据结构之一,在算法面试中占据着举足轻重的地位。力扣hot100中链表相关题目不仅考察对链表基本操作的掌握,更深入检验指针操作、边界处理与算法优化能力。本专题精选五道经典题目------反转链表、环形链表、环形链表 II、合并两个有序链表、删除链表的倒数第N个节点,系统讲解链表操作的核心技巧与解题模式。

一、链表数据结构基础

1.1 链表的特点与分类

链表是一种线性数据结构,通过指针将一系列节点串联起来。每个节点包含两部分:数据域(存储数据)和指针域(指向下一个节点)。与数组相比,链表的内存分配是非连续的,这使得链表在插入和删除操作上具有 O ( 1 ) O(1) O(1) 的时间复杂度优势,但随机访问需要 O ( n ) O(n) O(n) 时间。

链表主要分为以下几类:

  • 单链表:每个节点只有一个指针,指向后继节点。
  • 双链表:每个节点有两个指针,分别指向前驱和后继节点。
  • 循环链表:尾节点的指针指向头节点,形成环状结构。

在算法面试中,单链表最为常见,本专题所有题目均基于单链表实现。

:链表节点的定义通常如下(Python):

python 复制代码
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

1.2 链表与数组的对比

理解链表与数组的差异有助于在不同场景中选择合适的数据结构。下表总结了二者的核心区别:

特性 数组 链表
内存分配 连续 非连续
随机访问 O ( 1 ) O(1) O(1) O ( n ) O(n) O(n)
插入/删除 O ( n ) O(n) O(n)(需移动元素) O ( 1 ) O(1) O(1)(已知节点指针)
空间开销 固定大小,可能浪费或不足 动态分配,每个节点需额外指针空间
缓存友好性 高(局部性原理) 低(节点分散)

:虽然链表插入删除时间复杂度为 O ( 1 ) O(1) O(1),但前提是已知目标节点的前驱节点指针。若需按位置操作,仍需遍历找到前驱节点,整体复杂度仍为 O ( n ) O(n) O(n)。

1.3 链表操作的核心技巧

链表问题的解决往往依赖于几种经典技巧,掌握这些技巧能大幅提升解题效率:

  1. 迭代与递归:遍历链表的基础方法,递归在处理反转、合并等问题时尤为简洁。
  2. 快慢指针:用两个指针以不同速度遍历链表,用于检测环、寻找中点等。
  3. 虚拟头节点:在链表头部添加一个不存储值的节点,简化边界处理(如删除操作)。
  4. 双指针:两个指针协同移动,用于反转、分割等操作。
  5. 哈希表:存储已访问节点,用于检测环或重复节点。

:虚拟头节点(dummy head)是链表问题中极其重要的技巧。它避免了单独处理头节点变更的情况,使代码更加统一简洁。

1.4 链表基本操作实现

在深入解题前,我们先回顾链表的基本操作。这些操作是构建复杂算法的基础,面试官也可能直接考察其实现。

1.4.1 链表遍历

遍历链表是所有操作的基础。迭代法与递归法如下:

python 复制代码
# 迭代遍历
def traverse_iterative(head: ListNode):
    curr = head
    while curr:
        # 处理当前节点
        print(curr.val)
        curr = curr.next

# 递归遍历
def traverse_recursive(head: ListNode):
    if not head:
        return
    # 处理当前节点
    print(head.val)
    traverse_recursive(head.next)

:递归遍历的空间复杂度为 O ( n ) O(n) O(n)(递归栈),通常迭代法更优。

1.4.2 插入节点

在链表中插入节点分三种情况:头部插入、尾部插入、中间插入。

python 复制代码
# 头部插入:将新节点作为链表头
def insert_at_head(head: ListNode, val: int) -> ListNode:
    new_node = ListNode(val)
    new_node.next = head
    return new_node

# 尾部插入:将新节点追加到链表末尾
def insert_at_tail(head: ListNode, val: int) -> ListNode:
    new_node = ListNode(val)
    if not head:
        return new_node
    
    curr = head
    while curr.next:
        curr = curr.next
    curr.next = new_node
    return head

# 中间插入:在指定位置(0-index)插入
def insert_at_position(head: ListNode, val: int, pos: int) -> ListNode:
    if pos == 0:
        return insert_at_head(head, val)
    
    dummy = ListNode(-1, head)
    curr = dummy
    for _ in range(pos):
        if not curr.next:
            break
        curr = curr.next
    
    new_node = ListNode(val)
    new_node.next = curr.next
    curr.next = new_node
    return dummy.next

:插入操作的关键是找到正确的前驱节点。使用虚拟头节点可以统一头部插入和中间插入的逻辑。

1.4.3 删除节点

删除节点同样需考虑位置,并注意释放内存(Python中自动回收)。

python 复制代码
# 删除头部节点
def delete_head(head: ListNode) -> ListNode:
    if not head:
        return None
    return head.next

# 删除尾部节点
def delete_tail(head: ListNode) -> ListNode:
    if not head or not head.next:
        return None
    
    curr = head
    while curr.next.next:
        curr = curr.next
    curr.next = None
    return head

# 删除指定值的第一个节点
def delete_value(head: ListNode, val: int) -> ListNode:
    dummy = ListNode(-1, head)
    curr = dummy
    while curr.next:
        if curr.next.val == val:
            curr.next = curr.next.next
            break
        curr = curr.next
    return dummy.next

:删除节点时,若需删除多个节点,可使用循环;若需删除所有匹配节点,则需遍历整个链表。

1.4.4 查找与修改

查找节点、获取长度、修改值等辅助操作:

python 复制代码
# 获取链表长度
def get_length(head: ListNode) -> int:
    length = 0
    curr = head
    while curr:
        length += 1
        curr = curr.next
    return length

# 查找第k个节点(0-index)
def get_node_at_index(head: ListNode, k: int) -> ListNode:
    if k < 0:
        return None
    curr = head
    for _ in range(k):
        if not curr:
            return None
        curr = curr.next
    return curr

# 判断链表是否包含某个值
def contains(head: ListNode, val: int) -> bool:
    curr = head
    while curr:
        if curr.val == val:
            return True
        curr = curr.next
    return False

掌握这些基本操作后,我们可以更自信地应对复杂链表问题。

二、力扣hot100链表操作题目解析

2.1 反转链表(206)

2.1.1 题目概述与链接

题目:反转一个单链表。

链接LeetCode 206. Reverse Linked List

示例

复制代码
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
2.1.2 核心解题思路分析

反转链表是链表操作中最基础的题目之一,有两种主流解法:迭代法和递归法。

迭代法 :维护三个指针 prevcurrnext,遍历链表并逐个反转指针方向。核心步骤如下:

  1. 初始化 prev = Nonecurr = head
  2. 遍历链表,每次保存 next = curr.next
  3. curr.next 指向 prev
  4. prevcurr 分别向前移动一位
  5. 循环结束后,prev 即为新链表的头节点

递归法 :将问题分解为反转子链表。假设已经反转了 head.next 之后的链表,此时只需将 head 节点的下一个节点指向 head,并将 head.next 置为 None

:递归法虽然简洁,但空间复杂度为 O ( n ) O(n) O(n)(递归栈深度),迭代法则为 O ( 1 ) O(1) O(1)。面试中通常要求掌握两种方法。

2.1.3 时间复杂度与空间复杂度
  • 迭代法 :时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)
  • 递归法 :时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)(递归栈)
2.1.4 Python代码实现
python 复制代码
# 迭代法
def reverseList_iterative(head: ListNode) -> ListNode:
    prev = None
    curr = head
    while curr:
        next_node = curr.next  # 保存下一个节点
        curr.next = prev       # 反转指针
        prev = curr            # 移动prev
        curr = next_node       # 移动curr
    return prev

# 递归法
def reverseList_recursive(head: ListNode) -> ListNode:
    # 递归终止条件:空链表或只有一个节点
    if not head or not head.next:
        return head
    
    # 反转剩余链表
    new_head = reverseList_recursive(head.next)
    # 将当前节点的下一个节点指向当前节点
    head.next.next = head
    # 断开原方向
    head.next = None
    
    return new_head

:递归法的核心理解在于 head.next.next = head 这一行。此时 head.next 是已反转子链表的尾节点,将其指向 head 即完成了当前节点的反转。

2.1.5 优化思路与变式讨论
  • 局部反转:若要求反转链表的一部分(如从第 m 个节点到第 n 个节点),可先找到第 m-1 个节点,然后反转区间内的节点,最后重新连接。
  • 每 k 个一组反转:这是反转链表的进阶题目(LeetCode 25),需要结合递归与迭代,在反转每一组的同时保持组间连接。
2.1.6 面试考点与注意事项
  1. 边界处理:空链表、单节点链表的情况。
  2. 指针丢失 :在迭代法中,必须先保存 curr.next 再反转,否则会丢失后续节点。
  3. 递归深度:链表过长时递归可能导致栈溢出,需说明迭代法的优越性。
  4. 空间复杂度分析:明确区分迭代与递归的空间开销。

2.2 环形链表(141)

2.2.1 题目概述与链接

题目:给定一个链表,判断链表中是否有环。

链接LeetCode 141. Linked List Cycle

示例

复制代码
输入:head = [3,2,0,-4], pos = 1(尾节点连接到索引为1的节点)
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
2.2.2 核心解题思路分析

检测链表是否有环是快慢指针的经典应用。设置两个指针 slowfast,初始都指向头节点。slow 每次移动一步,fast 每次移动两步。如果链表中存在环,快慢指针必定会相遇(类似于跑步套圈);如果无环,fast 会先到达链表末尾(None)。

数学原理 :假设环外部分长度为 a a a,环长度为 b b b。当 slow 进入环时,fast 已在环内。设此时 fast 落后 slow 的距离为 c c c( 0 ≤ c < b 0 \le c < b 0≤c<b)。由于每步 fastslow 多走一步,经过 k k k 步后距离减少 k k k,当 k = c k = c k=c 时两者相遇。

:快慢指针相遇只能证明有环,但相遇点不一定是环的入口。寻找入口需要额外步骤,见下一题。

2.2.3 时间复杂度与空间复杂度
  • 时间复杂度 : O ( n ) O(n) O(n)。最坏情况下,slow 遍历整个链表一次。
  • 空间复杂度 : O ( 1 ) O(1) O(1)。只使用了两个指针。
2.2.4 Python代码实现
python 复制代码
def hasCycle(head: ListNode) -> bool:
    if not head or not head.next:
        return False
    
    slow = head
    fast = head
    
    while fast and fast.next:
        slow = slow.next          # 慢指针走一步
        fast = fast.next.next     # 快指针走两步
        if slow == fast:          # 相遇说明有环
            return True
    
    return False  # 快指针到达末尾,无环

:循环条件 fast and fast.next 确保了 fast.next.next 不会出现 Nonenext 属性访问错误。

2.2.5 优化思路与变式讨论
  • 哈希表法 :遍历链表,将每个节点存入哈希表,若遇到重复节点则有环。时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)。面试中通常要求快慢指针解法。
  • 破坏链表法 :遍历时将节点值标记为特殊值(如 None),若遇到已标记节点则有环。但会修改原链表,不推荐。
2.2.6 面试考点与注意事项
  1. 空链表与单节点链表 :直接返回 False
  2. 快指针移动条件 :必须检查 fast.next 是否为空。
  3. 数学原理理解:能够推导快慢指针必然相遇。
  4. 空间复杂度对比:与哈希表法的优劣分析。

2.3 环形链表 II(142)

2.3.1 题目概述与链接

题目 :给定一个链表,返回链表开始入环的第一个节点。如果链表无环,则返回 None

链接LeetCode 142. Linked List Cycle II

示例

复制代码
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为1的节点
解释:链表中有一个环,其尾部连接到第二个节点。
2.3.2 核心解题思路分析

此题在141题基础上增加了寻找环入口的要求。使用快慢指针找到相遇点后,将其中一个指针移回头节点,然后两个指针以相同速度(每次一步)前进,再次相遇点即为环入口。

数学推导 :设环外长度 a a a,环长度 b b b,相遇时 slow 走了 a + m a + m a+m 步( m m m 为在环内走的步数),fast 走了 a + n a + n a+n 步( n n n 为在环内走的步数,且 n = m + k ⋅ b n = m + k \cdot b n=m+k⋅b, k k k 为整数)。由于 fast 速度是 slow 的两倍,有 2 ( a + m ) = a + n 2(a + m) = a + n 2(a+m)=a+n,化简得 a = n − 2 m = k ⋅ b − m a = n - 2m = k \cdot b - m a=n−2m=k⋅b−m。这表明从头节点到入口的距离 a a a 等于从相遇点继续走 k ⋅ b − m k \cdot b - m k⋅b−m 步。而 k ⋅ b − m k \cdot b - m k⋅b−m 相当于从相遇点走 b − m b - m b−m 步(因为 k ⋅ b k \cdot b k⋅b 是环的整数倍,回到原位置)。因此,将指针移回头节点,两个指针同步前进,必在入口相遇。

:此推导过程是面试中的高频考点,需熟练掌握。

2.3.3 时间复杂度与空间复杂度
  • 时间复杂度 : O ( n ) O(n) O(n)。最坏情况下遍历链表两次。
  • 空间复杂度 : O ( 1 ) O(1) O(1)。
2.3.4 Python代码实现
python 复制代码
def detectCycle(head: ListNode) -> ListNode:
    if not head or not head.next:
        return None
    
    # 第一阶段:检测是否有环并找到相遇点
    slow = head
    fast = head
    has_cycle = False
    
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            has_cycle = True
            break
    
    if not has_cycle:
        return None
    
    # 第二阶段:寻找环入口
    slow = head  # 一个指针移回头节点
    while slow != fast:
        slow = slow.next
        fast = fast.next
    
    return slow  # 相遇点即为环入口

:第二阶段循环条件为 slow != fast,因为相遇点就是入口。若链表无环,第一阶段已返回。

2.3.5 优化思路与变式讨论
  • 哈希表法 :同样可用,但空间复杂度 O ( n ) O(n) O(n)。
  • 标记法:遍历时修改节点值为特殊标记,但会破坏原数据。
2.3.6 面试考点与注意事项
  1. 数学推导能力:必须能够解释为什么第二次相遇点是入口。
  2. 代码简洁性:两阶段逻辑清晰,避免冗余判断。
  3. 边界情况:无环、环在头节点等特殊情况。

2.4 合并两个有序链表(21)

2.4.1 题目概述与链接

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

链接LeetCode 21. Merge Two Sorted Lists

示例

复制代码
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
2.4.2 核心解题思路分析

合并有序链表是归并排序的基础操作。使用双指针分别遍历两个链表,比较当前节点值,将较小值连接到新链表,并移动对应指针。当某一链表遍历完后,将另一链表的剩余部分直接接上。

技巧:使用虚拟头节点(dummy)简化代码,避免单独处理头节点选择。

递归法:比较两个链表头节点,将较小节点作为合并后链表的头,然后递归合并剩余部分。

:递归法代码极其简洁,但空间复杂度 O ( n ) O(n) O(n)(递归栈)。迭代法空间复杂度 O ( 1 ) O(1) O(1)。

2.4.3 时间复杂度与空间复杂度
  • 时间复杂度 : O ( n + m ) O(n + m) O(n+m),其中 n n n、 m m m 分别为两链表长度。
  • 空间复杂度
    • 迭代法: O ( 1 ) O(1) O(1)(不计结果链表)
    • 递归法: O ( n + m ) O(n + m) O(n+m)(递归栈深度)
2.4.4 Python代码实现
python 复制代码
# 迭代法
def mergeTwoLists_iterative(l1: ListNode, l2: ListNode) -> ListNode:
    dummy = ListNode(-1)  # 虚拟头节点
    curr = dummy
    
    while l1 and l2:
        if l1.val <= l2.val:
            curr.next = l1
            l1 = l1.next
        else:
            curr.next = l2
            l2 = l2.next
        curr = curr.next
    
    # 将剩余部分接上
    curr.next = l1 if l1 else l2
    
    return dummy.next

# 递归法
def mergeTwoLists_recursive(l1: ListNode, l2: ListNode) -> ListNode:
    if not l1:
        return l2
    if not l2:
        return l1
    
    if l1.val <= l2.val:
        l1.next = mergeTwoLists_recursive(l1.next, l2)
        return l1
    else:
        l2.next = mergeTwoLists_recursive(l1, l2.next)
        return l2

:虚拟头节点技巧在本题中尤为重要,它统一了链表头部插入的逻辑,避免了复杂的条件判断。

2.4.5 优化思路与变式讨论
  • 合并k个有序链表:本题的进阶版(LeetCode 23),可使用最小堆或分治合并。
  • 原地合并:若要求不使用额外空间(即修改原链表),迭代法已满足。
2.4.6 面试考点与注意事项
  1. 虚拟头节点的使用:解释其作用与优势。
  2. 递归与迭代的选择:分析两种方法的适用场景。
  3. 边界处理:一个或两个链表为空的情况。
  4. 稳定性:当两节点值相等时,通常优先选择第一个链表的节点以保持稳定性。

2.5 删除链表的倒数第N个节点(19)

2.5.1 题目概述与链接

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

链接LeetCode 19. Remove Nth Node From End of List

示例

复制代码
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
解释:删除倒数第二个节点(值为4)。
2.5.2 核心解题思路分析

删除倒数第 n 个节点的关键在于定位其前驱节点。由于链表无法随机访问,需要遍历确定长度后再遍历删除,但这样需要两次遍历。更优的解法是使用双指针(快慢指针),只需一次遍历:

  1. 创建虚拟头节点(便于删除头节点情况)。
  2. 快指针 fast 先走 n 步。
  3. 然后慢指针 slowfast 同步前进,直到 fast 到达链表末尾。
  4. 此时 slow.next 即为倒数第 n 个节点,删除之。

:删除节点需要其前驱节点指针,因此 slow 应停留在待删除节点的前一个节点。

2.5.3 时间复杂度与空间复杂度
  • 时间复杂度 : O ( n ) O(n) O(n),一次遍历。
  • 空间复杂度 : O ( 1 ) O(1) O(1)。
2.5.4 Python代码实现
python 复制代码
def removeNthFromEnd(head: ListNode, n: int) -> ListNode:
    dummy = ListNode(-1, head)  # 虚拟头节点,指向原链表头
    fast = dummy
    slow = dummy
    
    # 快指针先走n步
    for _ in range(n):
        fast = fast.next
        if not fast:  # n大于链表长度(题目保证有效,但防御性编程)
            return head
    
    # 快慢指针同步前进,直到fast到达末尾
    while fast.next:
        slow = slow.next
        fast = fast.next
    
    # 删除slow.next节点
    slow.next = slow.next.next
    
    return dummy.next

for 循环中的 if not fast 是防御性代码。题目已保证 n 有效,但面试中可提及此边界考虑。

2.5.5 优化思路与变式讨论
  • 两次遍历法 :先计算链表长度 L,再遍历到第 L-n 个节点删除。同样 O ( n ) O(n) O(n) 时间,但需两次遍历。
  • 递归法 :递归遍历链表,利用递归栈反向计数,但空间复杂度 O ( n ) O(n) O(n)。
  • 删除倒数第n个节点变式:如要求只允许遍历一次,双指针法是标准答案。
2.5.6 面试考点与注意事项
  1. 虚拟头节点的必要性:当删除头节点时,普通方法需要特殊处理,虚拟头节点统一逻辑。
  2. 指针移动细节:快指针先走 n 步,慢指针起始位置为虚拟头节点。
  3. 边界条件:n 等于链表长度(删除头节点)、n=1(删除尾节点)等情况。
  4. 一次遍历证明:解释为什么双指针法只需一次遍历。

2.6 相交链表(160)

2.6.1 题目概述与链接

题目:编写一个程序,找到两个单链表相交的起始节点。

链接LeetCode 160. Intersection of Two Linked Lists

示例

复制代码
链表A: 4->1->8->4->5
链表B: 5->0->1->8->4->5
相交节点:节点值为8的节点(注意:相交基于引用,而非值)
2.6.2 核心解题思路分析

寻找两个链表相交节点的问题有多种解法:

  1. 哈希表法 :遍历链表A,将所有节点存入哈希表;然后遍历链表B,检查节点是否在哈希表中。时间复杂度 O ( n + m ) O(n+m) O(n+m),空间复杂度 O ( n ) O(n) O(n)。
  2. 双指针法(最优) :使用两个指针 pApB 分别从链表A和链表B的头节点出发。当 pA 到达链表末尾时,重定位到链表B的头节点;同理 pB 到达末尾时重定位到链表A的头节点。若两链表相交,则 pApB 必在相交节点相遇;若不相交,则最终同时到达 None

数学原理 :设链表A独有部分长度为 a a a,链表B独有部分长度为 b b b,公共部分长度为 c c c。指针 pA 走过的总路径为 a + c + b a + c + b a+c+b,pB 走过的总路径为 b + c + a b + c + a b+c+a,两者相等。因此,若相交,则会在第二轮的公共部分相遇;若不相交,则 c = 0 c=0 c=0,两指针同时到达 None

:双指针法的关键在于理解"路程相等"原理。无论链表长度如何,两个指针走过相同总路程后,要么在相交点相遇,要么同时到达末尾。

2.6.3 时间复杂度与空间复杂度
  • 双指针法 :时间复杂度 O ( n + m ) O(n+m) O(n+m),空间复杂度 O ( 1 ) O(1) O(1)
  • 哈希表法 :时间复杂度 O ( n + m ) O(n+m) O(n+m),空间复杂度 O ( n ) O(n) O(n) 或 O ( m ) O(m) O(m)
2.6.4 Python代码实现
python 复制代码
def getIntersectionNode(headA: ListNode, headB: ListNode) -> ListNode:
    if not headA or not headB:
        return None
    
    pA, pB = headA, headB
    
    # 第一轮遍历
    while pA != pB:
        # 若pA到达末尾,则转至链表B
        pA = pB if not pA else pA.next
        # 若pB到达末尾,则转至链表A
        pB = pA if not pB else pB.next
    
    # 若相交,pA/pB为相交节点;若不相交,pA/pB为None
    return pA

:上述代码中的 pA = pB if not pA else pA.next 是一种简洁写法,等价于:

python 复制代码
if not pA:
    pA = headB
else:
    pA = pA.next
2.6.5 优化思路与变式讨论
  • 长度对齐法 :先计算两链表长度差 d d d,让长链表的指针先走 d d d 步,然后两指针同步前进,相遇点即为交点。复杂度相同,但逻辑更直观。
  • 环形相交:若链表有环,需先判断环入口,再应用上述方法。
  • 多链表相交:扩展到多个链表寻找公共交点,可使用哈希表记录节点出现次数。
2.6.6 面试考点与注意事项
  1. 理解引用相等:相交节点是同一对象(内存地址相同),而非值相同。
  2. 边界情况:一个链表为空、两链表不相交、相交于头节点等。
  3. 空间复杂度要求 :若要求 O ( 1 ) O(1) O(1) 空间,必须使用双指针法。
  4. 证明能力:能够解释为什么双指针法有效。

三、链表操作模式总结

通过对五道典型题目的解析,我们可以提炼出链表问题的通用解题模式:

  1. 指针操作类(反转、删除):核心是维护多个指针,注意指针丢失和边界处理。
  2. 快慢指针类(环检测、找中点):设置不同速度的指针,用于解决与位置、环相关的问题。
  3. 虚拟头节点类(合并、删除):简化边界条件,使代码统一简洁。
  4. 递归与迭代选择:递归代码简洁但空间开销大,迭代空间高效但逻辑稍复杂。

下表总结了各题目的核心技巧与复杂度:

题目 核心技巧 时间复杂度 空间复杂度 备注
反转链表 迭代/递归指针反转 O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)/ O ( n ) O(n) O(n) 基础中的基础
环形链表 快慢指针 O ( n ) O(n) O(n) O ( 1 ) O(1) O(1) 经典套圈模型
环形链表 II 快慢指针+数学推导 O ( n ) O(n) O(n) O ( 1 ) O(1) O(1) 推导过程常考
合并有序链表 双指针+虚拟头节点 O ( n + m ) O(n+m) O(n+m) O ( 1 ) O(1) O(1)/ O ( n + m ) O(n+m) O(n+m) 归并基础操作
删除倒数第N节点 快慢指针+虚拟头节点 O ( n ) O(n) O(n) O ( 1 ) O(1) O(1) 一次遍历技巧

:链表问题的变式极多,但万变不离其宗。掌握上述模式后,面对新题目时只需识别其属于哪一类,即可快速套用相应解法。

四、面试准备建议

链表专题的备考建议如下:

  1. 基础优先:务必熟练掌握反转、合并、环检测等基础题目,面试官常以此作为热身题。
  2. 理解原理:不仅会写代码,更要理解背后的数学原理(如快慢指针的相遇证明)。
  3. 复杂度分析:对每道题目都能准确分析时间与空间复杂度,并比较不同解法的优劣。
  4. 边界处理:链表问题边界情况多(空链表、单节点、头尾操作等),代码需健壮。
  5. 变式拓展:练习相关变式题目,如局部反转、合并k个链表、寻找相交节点等。
  6. 白板编码:多在白板或纸上练习,注意指针画图辅助思考。

最后,链表问题虽基础但易错,平时练习时务必自己构造测试用例,涵盖各种边界情况,培养一次写对的习惯。

相关推荐
秋刀鱼不做梦2 小时前
网络编程和Socket套接字(UDP+TCP)(如果想知道Java中有关网络编程和Socket套接字的知识,那么只看这一篇就足够了!)
网络·网络协议·学习·tcp/ip·udp
njidf2 小时前
C++与量子计算模拟
开发语言·c++·算法
老鼠只爱大米2 小时前
LeetCode经典算法面试题 #70:爬楼梯(朴素递归、记忆化递归、动态规划等六种实现方案详解)
算法·leetcode·动态规划·递归·斐波那契·矩阵快速幂·爬楼梯
东离与糖宝2 小时前
面试官直言:Java应届生面试,我只看这3个核心能力
java·面试
徐某人..2 小时前
基于i.MX6ULL开发板与OV5640摄像头实现QT相机应用开发
qt·学习·arm
我材不敲代码2 小时前
OpenCV 光流估计实战:Lucas-Kanade 算法实现运动目标跟踪
opencv·算法·目标跟踪
说给风听.2 小时前
从零学会 Java 异常处理 —— 核心语法、自定义异常与面试指南
java·开发语言·面试
是翔仔呐2 小时前
第10章 串口通信USART全解:轮询/中断/DMA三种收发模式与上位机通信实战
c语言·开发语言·stm32·单片机·嵌入式硬件·学习·gitee