链表基础原理与题目说明
2026/4/18 3:41:31 网站建设 项目流程

链表基础原理与题目说明

文章目录

  • 链表基础原理与题目说明
    • 一、 什么是链表?
      • 1.1 链表的组成与分类
      • 1.2 链表 vs 数组核心对比
    • 二、 Python 中的链表实现与基础操作
      • 2.1 单向/双向节点定义
      • 2.2 链表基础流转操作
    • 三、 基础操作与哈希表应用
      • [160. 相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists/)
      • [141. 环形链表](https://leetcode.cn/problems/linked-list-cycle/) & [142. 环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/)
      • [138. 随机链表的复制](https://leetcode.cn/problems/copy-list-with-random-pointer/)
    • 四、 链表中的双指针与虚拟头节点技巧
      • [234. 回文链表](https://leetcode.cn/problems/palindrome-linked-list/)
      • [21. 合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/)
      • [2. 两数相加](https://leetcode.cn/problems/add-two-numbers/)
      • [19. 删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/)
    • 五、 链表的高阶反转与交换
      • [206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/)
      • [24. 两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/)
      • [25. K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/)
    • 六、 链表的排序与合并
      • [23. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/)
      • [148. 排序链表](https://leetcode.cn/problems/sort-list/)
    • 七、 综合数据结构设计
      • [146. LRU 缓存](https://leetcode.cn/problems/lru-cache/)

🔗 查看完整专栏(LeetCode基础算法专栏)

特别说明:

本文为个人的 LeetCode 刷题与学习笔记,内容仅供学习与交流使用,禁止转载或用于商业用途。需要强调的是,文中的题目解法不一定是最优解(可能存在时间或空间复杂度的进一步优化空间),主要目的是分享个人的解题思路与逻辑实现,仅供参考。笔记内容为个人理解与总结,可能存在疏漏或偏差,欢迎读者自行甄别并交流探讨。

一、 什么是链表?

链表(Linked List)是一种线性数据结构。与数组不同,链表中的元素在内存中并不是连续存储的,而是依靠指针引用把零散的节点(Node)串联起来。

1.1 链表的组成与分类

以最常见的单链表为例,每个节点通常包含两部分:

  1. 数据域(val):存储实际的值(如数字、字符串)。
  2. 指针域(next):存储下一个节点的内存地址(引用),相当于“指向”下一个节点的“箭头”。
链表类型特点说明结构示意图
单链表节点只有next指针,只能单向顺序遍历。head → Node1 → Node2 → None
双向链表节点包含prevnext指针,支持双向遍历。None ← Node1 ↔ Node2 ↔ Node3 → None
循环链表尾节点的next指向头节点,形成逻辑闭环。head → Node1 → Node2 → head

1.2 链表 vs 数组核心对比

对比维度数组(Array)链表(Linked List)
存储方式内存中连续分配内存中零散分配,靠指针串联
访问元素随机访问,时间复杂度O ( 1 ) O(1)O(1)顺序访问,必须从头遍历,时间复杂度O ( n ) O(n)O(n)
插入/删除需移动后续大量元素,时间复杂度O ( n ) O(n)O(n)只需修改相邻指针,时间复杂度O ( 1 ) O(1)O(1)
空间占用长度固定(动态数组存在扩容开销),易浪费按需动态分配,但每个节点需额外存储指针(空间开销略大)

二、 Python 中的链表实现与基础操作

Python 没有内置的原生链表结构,但可以通过面向对象中的「类」来完美模拟节点与链表操作。

2.1 单向/双向节点定义

# 定义单链表节点classListNode:def__init__(self,val=0,next=None):self.val=val# 数据域self.next=next# 指针域# 定义双向链表节点(如 LRU 缓存中会用到)classDLinkedNode:def__init__(self,key=0,value=0):self.key=key# 键(用于哈希表反向删除)self.value=value self.pre=None# 前驱指针self.next=None# 后继指针

2.2 链表基础流转操作

# 1. 创建链表:1 → 3 → 5 → Nonenode1=ListNode(1)node2=ListNode(3)node3=ListNode(5)node1.next=node2 node2.next=node3 head=node1# 设定链表入口# 2. 插入节点:在 1 和 3 之间插入 2new_node=ListNode(2)node1.next=new_node new_node.next=node2# 3. 删除节点:删除节点 2node1.next=node2# 节点1直接指向节点3,跳过新节点# 4. 遍历链表current=headwhilecurrent:print(current.val,end=" → ")current=current.next

三、 基础操作与哈希表应用

在链表的基础题目中,哈希表(Set/Dict)常被用来记录节点的内存引用,从而快速解决环路检测、相交判断及深拷贝问题。

160. 相交链表

解题思路

存储链表 A 的所有节点引用(而非节点值)到哈希集合set中。随后遍历链表 B,返回第一个在集合中命中的节点;若无相交则返回None

核心代码

fromtypingimportOptionalclassSolution:defgetIntersectionNode(self,headA:ListNode,headB:ListNode)->Optional[ListNode]:node_set=set()current=headA# 1. 记录链表A所有节点的内存引用whilecurrent:node_set.add(current)current=current.next# 2. 遍历链表B寻找首个重合节点current=headBwhilecurrent:ifcurrentinnode_set:returncurrent current=current.nextreturnNone

141. 环形链表 & 142. 环形链表 II

解题思路

利用哈希集合「存储节点引用 + 快速判断存在性」的特性。遍历链表,若发现当前节点已存在于集合中,则说明存在环(返回 True 或该节点);若遍历到None,则无环。

核心代码(以142题寻找入环点为例)

fromtypingimportOptionalclassSolution:defdetectCycle(self,head:Optional[ListNode])->Optional[ListNode]:hashdict=set()current=headwhilecurrent:ifcurrentinhashdict:returncurrent# 找到入环点。如果是141题,这里返回 Truehashdict.add(current)current=current.nextreturnNone# 无环。如果是141题,这里返回 False

138. 随机链表的复制

解题思路

利用哈希表建立原节点 → 复制节点的映射。分两步复制:先仅复制节点值(建立映射),再通过映射关系更新复制节点的nextrandom指针。使用dict.get()可完美兼容指针为None的边界情况。

核心代码

fromtypingimportOptionalclassSolution:defcopyRandomList(self,head:'Optional[Node]')->'Optional[Node]':hashdict={}# 第一次遍历:复制值,构建映射cur_node=headwhilecur_node:hashdict[cur_node]=Node(cur_node.val)cur_node=cur_node.next# 第二次遍历:通过映射关系连接指针cur_node=headwhilecur_node:hashdict[cur_node].next=hashdict.get(cur_node.next)hashdict[cur_node].random=hashdict.get(cur_node.random)cur_node=cur_node.nextreturnhashdict.get(head)

四、 链表中的双指针与虚拟头节点技巧

234. 回文链表

解题思路

利用「数组可随机访问」的特性,将链表的线性遍历转换为数组存储,彻底解决单链表无法反向遍历的问题。随后利用左右双指针向中间靠拢判断回文。

核心代码

fromtypingimportOptionalclassSolution:defisPalindrome(self,head:Optional[ListNode])->bool:listarray=[]current=headwhilecurrent:listarray.append(current.val)current=current.nextl,r=0,len(listarray)-1whilel<=r:iflistarray[l]!=listarray[r]:returnFalsel+=1r-=1returnTrue

21. 合并两个有序链表

解题思路

**虚拟头节点(Dummy Node)**是处理链表拼接的神技。双指针分别遍历两个链表,逐个比较并拼接到虚拟头节点后,最后追加未遍历完的剩余部分。

核心代码

fromtypingimportOptionalclassSolution:defmergeTwoLists(self,list1:Optional[ListNode],list2:Optional[ListNode])->Optional[ListNode]:temp_node=cur_node=ListNode(0)# 虚拟头节点whilelist1andlist2:iflist1.val<list2.val:cur_node.next=list1 list1=list1.nextelse:cur_node.next=list2 list2=list2.nextcur_node=cur_node.next# 拼接剩余有序部分cur_node.next=list1iflist1elselist2returntemp_node.next

2. 两数相加

解题思路

逆序存储天然契合“从个位数开始进位加法”的数学逻辑。维护一个进位标记carry,双指针同步遍历。空节点按 0 处理,直到两链表走完且进位为 0 时结束。

核心代码

fromtypingimportOptionalclassSolution:defaddTwoNumbers(self,l1:Optional[ListNode],l2:Optional[ListNode])->Optional[ListNode]:dummy=cur=ListNode(0)carry=0whilel1orl2orcarry:val1=l1.valifl1else0val2=l2.valifl2else0total=val1+val2+carry cur_val=total%10carry=total//10cur.next=ListNode(cur_val)cur=cur.nextifl1:l1=l1.nextifl2:l2=l2.nextreturndummy.next

19. 删除链表的倒数第 N 个结点

解题思路

利用栈(Stack)“先进后出”的特性(此处分享栈解法,快慢指针亦可)。先将所有节点压入栈中,随后弹出n个节点,此时栈顶即为待删除节点的前驱节点。搭配虚拟头节点可完美处理删除原链表头节点的边界情况。

核心代码

fromtypingimportOptionalclassSolution:defremoveNthFromEnd(self,head:Optional[ListNode],n:int)->Optional[ListNode]:dummy_node=ListNode(0,head)stack=[]# 所有节点入栈(包含虚拟头)current=dummy_nodewhilecurrent:stack.append(current)current=current.next# 弹出后续的 n 个节点for_inrange(n):stack.pop()# 此时栈顶即为待删除节点的前驱pre_node=stack[-1]pre_node.next=pre_node.next.nextreturndummy_node.next

五、 链表的高阶反转与交换

206. 反转链表

解题思路

经典双指针迭代法。定义pre_node(初始为None)和cur_node(初始为head)。每次遍历时,先用temp_node留住后继节点,再原地将当前节点的next掉头指向前驱。

核心代码

fromtypingimportOptionalclassSolution:defreverseList(self,head:Optional[ListNode])->Optional[ListNode]:pre_node=Nonecur_node=headwhilecur_node:temp_node=cur_node.next# 留住后路cur_node.next=pre_node# 指针反转# 整体平移pre_node=cur_node cur_node=temp_nodereturnpre_node

24. 两两交换链表中的节点

解题思路

提供迭代法。借助虚拟头节点,每次确保有两个相邻节点可交换(cur.nextcur.next.next)。在交换时,注意指针断开与重连的顺序,避免链表断裂。

核心代码

fromtypingimportOptionalclassSolution:defswapPairs(self,head:Optional[ListNode])->Optional[ListNode]:dummy_node=ListNode(0,head)cur_node=dummy_nodewhilecur_node.nextandcur_node.next.next:node1=cur_node.nextnode2=cur_node.next.next# 核心穿针引线逻辑cur_node.next=node2 node1.next=node2.nextnode2.next=node1# 步进,准备下一对cur_node=node1returndummy_node.next

(注:本题也可以采用递归拆解子问题:处理当前对节点的交换,剩余链表交由递归完成拼接)

25. K 个一组翻转链表

解题思路

分治与递归思想。自定义reverselr(l, r)函数实现左闭右开区间[l, r)的就地反转。主函数中通过循环定位k个元素的右边界,调用反转函数,并将反转后的尾节点衔接下一轮递归返回的结果。

核心代码

fromtypingimportOptionalclassSolution:defreverseKGroup(self,head:Optional[ListNode],k:int)->Optional[ListNode]:# 辅助函数:翻转 [l, r) 区间内的节点defreverselr(l,r):pre_node=Nonecur_node=lwhilecur_node!=r:next_node=cur_node.nextcur_node.next=pre_node pre_node=cur_node cur_node=next_nodereturnpre_nodeifnothead:returnNonel=r=head# 寻找当前组的右边界for_inrange(k):ifnotr:returnhead# 不足 k 个,保持原样返回r=r.next# 翻转当前组newhead=reverselr(l,r)# 递归衔接剩余链表l.next=self.reverseKGroup(r,k)returnnewhead

六、 链表的排序与合并

23. 合并 K 个升序链表

解题思路

将“合并 K 个”降维打击为“多次合并两个”。复用两两合并的代码逻辑,通过一个初始为None的结果链表,遍历列表数组不断累积合并。(也可使用优先队列优化至O ( N log ⁡ K ) O(N \log K)O(NlogK)

核心代码

fromtypingimportList,OptionalclassSolution:defmergeKLists(self,lists:List[Optional[ListNode]])->Optional[ListNode]:# 辅助函数:合并两个升序链表defmerge2list(list1,list2):dummy_node=cur_node=ListNode(0)whilelist1andlist2:iflist1.val<list2.val:cur_node.next=list1 list1=list1.nextelse:cur_node.next=list2 list2=list2.nextcur_node=cur_node.nextcur_node.next=list1iflist1elselist2returndummy_node.nextans=Noneforlinlists:ans=merge2list(ans,l)returnans

148. 排序链表

解题思路

采用自顶向下的归并排序,满足O ( N log ⁡ N ) O(N \log N)O(NlogN)复杂度要求。利用快慢指针找中点将链表一分为二(务必将前一段的next截断置空),分别递归排序,最后再合并有序链表。

核心代码

fromtypingimportOptionalclassSolution:defsortList(self,head:Optional[ListNode])->Optional[ListNode]:ifheadisNoneorhead.nextisNone:returnhead# 1. 快慢指针找中点slow,fast=head,head.nextwhilefastandfast.next:slow=slow.nextfast=fast.next.next# 2. 截断链表分为两半mid=slow.nextslow.next=None# 3. 分别递归排序left_list=self.sortList(head)right_list=self.sortList(mid)# 4. 合并两个有序链表dummy_node=cur_node=ListNode(0)whileleft_listandright_list:ifleft_list.val<right_list.val:cur_node.next=left_list left_list=left_list.nextelse:cur_node.next=right_list right_list=right_list.nextcur_node=cur_node.nextcur_node.next=left_listifleft_listelseright_listreturndummy_node.next

七、 综合数据结构设计

146. LRU 缓存

解题思路

LRU 缓存机制的灵魂在于:哈希表 + 双向链表

核心代码

classDLinkedNode:def__init__(self,key=0,value=0):self.key=key# 冗余存储key,用于淘汰时反向删除哈希表记录self.value=value self.pre=Noneself.next=NoneclassLRUCache:def__init__(self,capacity:int):self.hashcache=dict()self.capacity=capacity self.size=0# 初始化伪头和伪尾节点,互相连接构成初始边界self.head=DLinkedNode()self.tail=DLinkedNode()self.head.next=self.tail self.tail.pre=self.headdefget(self,key:int)->int:ifkeynotinself.hashcache:return-1node=self.hashcache[key]self.moveToHead(node)# 访问过,提升为最近使用returnnode.valuedefput(self,key:int,value:int)->None:ifkeynotinself.hashcache:node=DLinkedNode(key,value)self.hashcache[key]=node self.addToHead(node)self.size+=1# 若超限,移除队尾(最久未使用)节点ifself.size>self.capacity:remove_node=self.removeTail()self.hashcache.pop(remove_node.key)self.size-=1else:node=self.hashcache[key]node.value=value self.moveToHead(node)# =============== 内部辅助指针操作 ===============defaddToHead(self,node):node.pre=self.head node.next=self.head.nextself.head.next.pre=node self.head.next=nodedefremoveNode(self,node):node.pre.next=node.nextnode.next.pre=node.predefmoveToHead(self,node):self.removeNode(node)self.addToHead(node)defremoveTail(self):node=self.tail.pre self.removeNode(node)returnnode

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询