【笔面试算法学习专栏】链表操作·基础三题精讲(206.反转链表、141.环形链表、21.合并两个有序链表)

引言与链表基础

链表(Linked List)是一种基础且重要的线性数据结构,与数组不同,链表中的元素在内存中并非连续存储,而是通过指针(或引用)将零散的内存块串联起来。每个链表节点(Node)包含两部分:数据域(存储数据)和指针域(存储下一个节点的地址)。这种存储方式赋予了链表动态插入/删除的高效性,但牺牲了随机访问的能力。

链表的核心特点

  • 内存非连续:节点分散在内存各处,通过指针连接。
  • 动态大小:无需预先指定容量,可随时申请新节点。
  • 操作效率
    • 插入/删除(已知节点位置): O ( 1 ) O(1) O(1)
    • 随机访问: O ( n ) O(n) O(n)(需从头遍历)
    • 空间开销:每个节点需额外存储指针。

常见链表类型

  1. 单链表(Singly Linked List):每个节点只有一个指针指向后继。
  2. 双链表(Doubly Linked List):节点含前后两个指针,可向前/向后遍历。
  3. 循环链表(Circular Linked List):尾节点指向头节点,形成环。

基础操作

  • 遍历:从头节点出发,依次访问每个节点。
  • 插入:在指定节点前/后插入新节点(需调整指针)。
  • 删除:移除指定节点,并保证链表不断开。
  • 查找:按值或位置查找节点。

本文选取力扣hot100中三道经典链表题目------206.反转链表141.环形链表21.合并两个有序链表,深入剖析其解题思路、代码实现与面试考点,帮助读者建立链表操作的思维框架,从容应对面试挑战。

206.反转链表

题目概述与链接

题目链接力扣206.反转链表
题意 :给定单链表的头节点 head,反转链表,并返回反转后的头节点。

示例

  • 输入:head = [1,2,3,4,5]
  • 输出:[5,4,3,2,1]

要求 :实现 O ( n ) O(n) O(n) 时间复杂度和 O ( 1 ) O(1) O(1) 空间复杂度(递归解法除外)。

核心解题思路

反转链表的本质是调整每个节点的指针方向 ,将"当前节点指向后继"改为"当前节点指向前驱"。有两种经典实现方式:迭代法递归法

迭代法(三指针法)

维护三个指针:

  • prev:指向已反转部分的新头节点。
  • curr:当前待反转节点。
  • next:保存 curr 的后继,防止断链。

步骤

  1. 初始化 prev = Nonecurr = head
  2. 遍历链表,在每一轮中:
    • 保存 next = curr.next
    • curr.next 指向 prev(反转指针)。
    • prevcurr 分别向后移动一位。
  3. currNone 时,prev 即为新头节点。

时间复杂度 : O ( n ) O(n) O(n),遍历一次链表。
空间复杂度 : O ( 1 ) O(1) O(1),仅使用常数个指针。

递归法

递归的思想是:假设后续链表已经反转,当前节点只需处理与后续部分的关系

步骤

  1. 递归基:若链表为空或只有一个节点,直接返回 head
  2. 递归反转 head.next 之后的链表,得到新头节点 new_head
  3. head.next.next 指向 head(反转指针),并将 head.next 置为 None(断开原连接)。
  4. 返回 new_head

时间复杂度 : O ( n ) O(n) O(n),递归深度为链表长度。
空间复杂度 : O ( n ) O(n) O(n),递归栈空间。

Python代码实现

迭代法
python 复制代码
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        prev, curr = None, head
        while curr:
            # 保存后继节点
            next_node = curr.next
            # 反转指针
            curr.next = prev
            # 移动指针
            prev, curr = curr, next_node
        return prev

代码说明

  • prev 始终指向已反转部分的头节点,curr 为当前待处理节点。
  • 每次循环将 curr.next 指向 prev,然后更新 prevcurr
  • 循环结束时 currNoneprev 指向原链表的尾节点(即新头节点)。
递归法
python 复制代码
class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        # 递归基
        if not head or not head.next:
            return head
        
        # 递归反转后续链表
        new_head = self.reverseList(head.next)
        
        # 反转当前节点与后续部分的关系
        head.next.next = head
        head.next = None
        
        return new_head

代码说明

  • 递归到链表末尾,返回尾节点作为新头节点 new_head
  • 回溯过程中,将当前节点的后继节点的 next 指向当前节点,实现局部反转。
  • 最后将原头节点的 next 置为 None,避免成环。

优化思路

  • 边界条件处理
    • 空链表:直接返回 None
    • 单节点链表:直接返回 head
  • 迭代法的哨兵节点:可使用哑节点简化操作,但本题直接操作指针更清晰。
  • 递归的栈溢出风险:链表过长时递归深度可能超过系统栈限制,优先使用迭代法。

面试考点

  1. 指针操作陷阱:反转指针时若未保存后继节点,会导致断链。
  2. 递归与迭代的选择:面试官可能要求同时给出两种解法,并分析优劣。
  3. 空间复杂度 :递归法的 O ( n ) O(n) O(n) 栈空间是否可接受?
  4. 扩展问题 :如何反转链表的前 N N N 个节点?如何每 k k k 个节点一组反转?

141.环形链表

题目概述与链接

题目链接力扣141.环形链表
题意 :给定链表的头节点 head,判断链表中是否存在环。如果链表中存在环,则返回 true;否则返回 false

示例

  • 输入:head = [3,2,0,-4](尾节点指向索引1的节点)
  • 输出:true

要求 :实现 O ( n ) O(n) O(n) 时间复杂度和 O ( 1 ) O(1) O(1) 空间复杂度。

核心解题思路

判断链表是否有环的经典方法是快慢指针(Floyd判圈算法)。其核心思想是:让两个指针以不同速度遍历链表,若链表有环,则快指针最终会追上慢指针(相遇);若无环,快指针会先到达链表尾部。

快慢指针算法
  • 慢指针 slow:每次移动一步。
  • 快指针 fast:每次移动两步。

步骤

  1. 初始化 slow = fast = head
  2. 循环条件:fastfast.next 均不为 None(保证可移动两步)。
  3. 每轮循环:
    • slow = slow.next
    • fast = fast.next.next
    • slow == fast,说明相遇,有环,返回 true
  4. 循环结束(fastfast.nextNone)说明无环,返回 false

数学证明

设链表头到环入口距离为 a a a,环入口到相遇点距离为 b b b,环长度为 c c c。

slow 进入环时,fast 已在环中。设此时 fast 领先 slow 距离 d d d( 0 ≤ d < c 0 \le d < c 0≤d<c)。

由于 fast 每步比 slow 多走1,经过 k k k 步后,fast 追上 slow,满足 k ≡ d ( m o d c ) k \equiv d \pmod{c} k≡d(modc)。

因此两者必在有限步内相遇。

时间复杂度 : O ( n ) O(n) O(n),最坏情况下遍历整个链表。
空间复杂度 : O ( 1 ) O(1) O(1),仅使用两个指针。

Python代码实现

python 复制代码
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        slow = fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if slow == fast:
                return True
        return False

代码说明

  • 使用 while fast and fast.next 保证快指针可安全移动两步。
  • 每次移动后检查 slow == fast,若相等则存在环。
  • 循环正常结束说明快指针已抵达链表末尾,无环。

优化思路

  • 不同环位置的检测效率 :若环靠近头部,快指针很快追上慢指针;若环在尾部,需要遍历较长时间。但最坏情况均为 O ( n ) O(n) O(n)。
  • 边界条件
    • 空链表或单节点无环:直接返回 false
    • 单节点自成环:head.next == head,快慢指针首次移动后即相遇。
  • 避免无限循环 :确保快指针每次移动两步,且检查 fast.next 不为空。

面试考点

  1. 数学证明:面试官可能要求证明快慢指针必然相遇。
  2. 环入口定位:扩展问题------如何找到环的入口节点(力扣142题)。
  3. 空间复杂度要求 :若使用哈希表存储已访问节点,空间为 O ( n ) O(n) O(n),不符合 O ( 1 ) O(1) O(1) 要求。
  4. 变种问题:如何判断两个链表是否相交?如何找到相交节点?

21.合并两个有序链表

题目概述与链接

题目链接力扣21.合并两个有序链表
题意 :将两个升序链表合并为一个新的升序链表并返回。新链表应通过拼接给定的两个链表节点组成。

示例

  • 输入:l1 = [1,2,4], l2 = [1,3,4]
  • 输出:[1,1,2,3,4,4]

要求 :时间复杂度 O ( n + m ) O(n+m) O(n+m),空间复杂度 O ( 1 ) O(1) O(1)(不计递归栈)。

核心解题思路

合并有序链表的本质是比较两个链表的当前节点,将较小者接入新链表 。有两种实现方式:迭代法递归法

迭代法(哨兵节点)

使用一个哑节点(dummy) 作为新链表的起始点,维护一个 tail 指针指向当前新链表的尾部。然后比较 l1l2 的当前节点:

  • l1.val <= l2.val,将 l1 节点接在 tail 后,l1 后移。
  • 否则,将 l2 节点接在 tail 后,l2 后移。
  • 每次操作后更新 tail 指针。
  • 当某一链表遍历完后,将 tail.next 指向另一链表的剩余部分。

时间复杂度 : O ( n + m ) O(n+m) O(n+m),遍历两个链表各一次。
空间复杂度 : O ( 1 ) O(1) O(1),仅使用常数个指针。

递归法

递归的思想是:每次比较两个链表的头节点,将较小者作为当前节点,然后递归合并剩余部分

步骤

  1. 递归基:若 l1 为空返回 l2,若 l2 为空返回 l1
  2. 比较 l1.vall2.val
    • l1.val <= l2.val,则 l1.next = merge(l1.next, l2),返回 l1
    • 否则 l2.next = merge(l1, l2.next),返回 l2

时间复杂度 : O ( n + m ) O(n+m) O(n+m),递归深度为两链表长度之和。
空间复杂度 : O ( n + m ) O(n+m) O(n+m),递归栈空间。

Python代码实现

迭代法
python 复制代码
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

class Solution:
    def mergeTwoLists(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
        dummy = ListNode(-1)  # 哑节点
        tail = dummy
        
        while l1 and l2:
            if l1.val <= l2.val:
                tail.next = l1
                l1 = l1.next
            else:
                tail.next = l2
                l2 = l2.next
            tail = tail.next
        
        # 将剩余部分直接接上
        tail.next = l1 if l1 else l2
        
        return dummy.next

代码说明

  • 哑节点 dummy 简化了头节点的处理,最后返回 dummy.next 即为新链表头。
  • tail 指针始终指向新链表的尾部,便于追加节点。
  • 循环结束后,将非空链表的剩余部分直接链接到 tail.next,无需逐个节点追加。
递归法
python 复制代码
class Solution:
    def mergeTwoLists(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
        if not l1:
            return l2
        if not l2:
            return l1
        
        if l1.val <= l2.val:
            l1.next = self.mergeTwoLists(l1.next, l2)
            return l1
        else:
            l2.next = self.mergeTwoLists(l1, l2.next)
            return l2

代码说明

  • 递归基直接返回非空链表,简洁明了。
  • 每次递归调用都将较小节点的 next 指向后续合并的结果,形成递归链。
  • 递归深度等于两链表节点总数,需注意栈溢出风险。

优化思路

  • 不同链表长度下的性能:迭代法在任何情况下都是最优选择,递归法在链表极长时可能栈溢出。
  • 空间优化 :迭代法使用 O ( 1 ) O(1) O(1) 额外空间,递归法为 O ( n + m ) O(n+m) O(n+m)。
  • 边界条件
    • 其中一个链表为空:直接返回另一个链表。
    • 两个链表均为空:返回 None
  • 稳定性 :若两节点值相等,优先选择 l1 节点可保持稳定性(原顺序)。

面试考点

  1. 链表操作熟练度:能否熟练使用哨兵节点简化代码?
  2. 递归思维:能否清晰描述递归合并的过程?
  3. 时空复杂度分析:能否准确分析迭代与递归的复杂度差异?
  4. 扩展问题 :如何合并 k k k 个有序链表?如何合并两个有序链表并去重?

总结与扩展

通过以上三道题目的深度解析,我们可归纳出链表问题的核心解题模式

  1. 指针操作:链表问题本质是指针的移动与连接。熟练掌握多指针(快慢指针、前后指针)技巧是解题关键。
  2. 递归思维:许多链表问题(如反转、合并)具有递归结构,利用递归可简化代码,但需注意栈空间开销。
  3. 边界处理:空链表、单节点链表、环的入口等边界情况需仔细考虑。

力扣链表经典题目延伸练习

  1. 19.删除链表的倒数第N个节点:快慢指针定位倒数第N个节点。
  2. 142.环形链表II:在判断有环的基础上,找到环的入口节点。
  3. 24.两两交换链表中的节点:递归或迭代实现相邻节点交换。
  4. 148.排序链表:归并排序在链表上的应用(快慢指针找中点)。
  5. 160.相交链表:双指针技巧,找到两个链表的相交节点。
相关推荐
啦啦啦!2 小时前
项目环境的搭建,项目的初步使用和deepseek的初步认识
开发语言·c++·人工智能·算法
算法鑫探2 小时前
2025 图形(蓝桥杯十六届C组程序题 C 题)
c语言·数据结构·算法·新人首发
田梓燊2 小时前
leetcode 54
算法·leetcode·职场和发展
wuweijianlove2 小时前
算法性能优化中的编译器指令重排影响的技术4
算法
沉鱼.442 小时前
第十五届题目
linux·运维·算法
华法林的小助手2 小时前
[学习笔记]在ros humble里使用qt
笔记·qt·学习
red_redemption2 小时前
自由学习记录(158)
学习
我头发多我先学2 小时前
C++ STL vector 原理到模拟实现
c++·算法
智慧化智能化数字化方案2 小时前
向华为学习——解读质量管理培训 IPD基础知识研发质量管理【附全文阅读】
学习·华为ipd流程·ipd基础知识·研发质量管理