第二章 链表
1. 基础知识
链表是⼀种通过指针串联在⼀起的线性结构,每⼀个节点由两部分组成,⼀个是数据域⼀个是指针域(存放指向下⼀个节点的指针),最后⼀个节点的指针域指向null(空指针的意思)。
链表的⼊⼝节点称为链表的头结点也就是####head
类型
单链表

单链表中的指针域只能指向节点的下⼀个节点。
双链表
每⼀个节点有两个指针域,⼀个指向下⼀个节点,⼀个指向上⼀个节点。

既可以向前查询也可以向后查询
循环链表
链表⾸尾相连。

循环链表可以⽤来解决约瑟夫环问题。
链表的存储⽅式
数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。
链表是通过指针域的指针链接在内存中各个节点。

这个链表起始节点为2, 终⽌节点为7, 各个节点分布在内存的不同地址空间上,通过指针串联在⼀起。
链表的定义
class ListNode:
def __init__(self, val, next=None):
self.val = val
self.next = next
链表的操作
删除节点

删除D节点,只要将C节点的next指针 指向E节点就可以了。
添加节点

以看出链表的增添和删除都是O(1)操作,也不会影响到其他节点。
但是要注意,要是删除第五个节点,需要从头节点查找到第四个节点通过next指针进⾏删除操作,查找的时间复杂
度是O(n)。
链表和数组性能分析

2. 移除链表元素
力扣题目链接
手把手带你学会操作链表 | LeetCode:203.移除链表元素
思路

因为单链表的特殊性,只能指向下⼀个节点,刚刚删除的是链表的中第⼆个,和第四个节点,那么如果删除的是头结点⼜该怎么办呢?
原链表删除元素

只要将头结点向后移动⼀位就可以,这样就从链表中移除了⼀个头结点
- 时间复杂度: O(n)
- 空间复杂度: O(1)
虚拟头结点
按照统⼀的⽅式进⾏移除

# 虚拟头节点法
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
# 创建虚拟头部节点以简化删除过程
dummy_head = ListNode(next = head)
# 遍历列表并删除值为val的节点
current = dummy_head
while current.next:
if current.next.val == val:
current.next = current.next.next
else:
current = current.next
return dummy_head.next
- 时间复杂度: O(n)
- 空间复杂度: O(1)
3. 设计链表
思路
设置虚拟头节点
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.dummy_head = ListNode()
self.size = 0
def get(self, index: int) -> int:
if index < 0 or index >= self.size:
return -1
current = self.dummy_head.next
for i in range(index):
current = current.next
return current.val
def addAtHead(self, val: int) -> None:
self.dummy_head.next = ListNode(val, self.dummy_head.next)
self.size += 1
def addAtTail(self, val: int) -> None:
current = self.dummy_head
while current.next:
current = current.next
current.next = ListNode(val)
self.size += 1
def addAtIndex(self, index: int, val: int) -> None:
if index < 0 or index > self.size:
return
current = self.dummy_head
for i in range(index):
current = current.next
current.next = ListNode(val, current.next)
self.size += 1
def deleteAtIndex(self, index: int) -> None:
if index < 0 or index >= self.size:
return
current = self.dummy_head
for i in range(index):
current = current.next
current.next = current.next.next
self.size -= 1
# Your MyLinkedList object will be instantiated and called as such:
# obj = MyLinkedList()
# param_1 = obj.get(index)
# obj.addAtHead(val)
# obj.addAtTail(val)
# obj.addAtIndex(index,val)
# obj.deleteAtIndex(index)
4. 翻转链表
力扣题目链接
帮你拿下反转链表 | LeetCode:206.反转链表 | 双指针法 | 递归法
思路
只需要改变链表的next指针的指向,直接将链表反转 ,⽽不⽤重新定义⼀个新的链表

双指针法
⾸先定义⼀个cur指针,指向头结点,再定义⼀个pre指针,初始化为null。
然后就要开始反转了,⾸先要把 cur->next 节点⽤tmp指针保存⼀下,也就是保存⼀下这个节点。
为什么要保存⼀下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第⼀个节点了。
接下来,就是循环⾛如下代码逻辑了,继续移动pre和cur指针。
最后, cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了, pre指针就指向了新的头结点。
(版本一)双指针法
# 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: ListNode) -> ListNode:
cur = head
pre = None
while cur:
temp = cur.next # 保存一下 cur的下一个节点,因为接下来要改变cur->next
cur.next = pre #反转
#更新pre、cur指针
pre = cur
cur = temp
return pre
递归法
(版本二)递归法
# 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: ListNode) -> ListNode:
return self.reverse(head, None)
def reverse(self, cur: ListNode, pre: ListNode) -> ListNode:
if cur == None:
return pre
temp = cur.next
cur.next = pre
return self.reverse(temp, cur)
5. 两两交换链表中的节点
帮你把链表细节学清楚! | LeetCode:24. 两两交换链表中的节点
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def swapPairs(self, head: ListNode) -> ListNode:
dummy_head = ListNode(next=head)
current = dummy_head
# 必须有cur的下一个和下下个才能交换,否则说明已经交换结束了
while current.next and current.next.next:
temp = current.next # 防止节点修改
temp1 = current.next.next.next
current.next = current.next.next
current.next.next = temp
temp.next = temp1
current = current.next.next
return dummy_head.next
还可以用递归的方式self.swapPairs()
# 递归版本
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head is None or head.next is None:
return head
# 待翻转的两个node分别是pre和cur
pre = head
cur = head.next
next = head.next.next
cur.next = pre # 交换
pre.next = self.swapPairs(next) # 将以next为head的后续链表两两交换
return cur
6. 删除链表的倒数第N个节点
链表遍历学清楚! | LeetCode:19.删除链表倒数第N个节点
思路
双指针的经典应⽤,如果要删除倒数第n个节点,让fast先移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
# 创建一个虚拟节点,并将其下一个指针设置为链表的头部
dummy_head = ListNode(0, head)
# 创建两个指针,慢指针和快指针,并将它们初始化为虚拟节点
slow = fast = dummy_head
# 快指针比慢指针快 n+1 步
for i in range(n+1):
fast = fast.next
# 移动两个指针,直到快速指针到达链表的末尾
while fast:
slow = slow.next
fast = fast.next
# 通过更新第 (n-1) 个节点的 next 指针删除第 n 个节点
slow.next = slow.next.next
return dummy_head.next
- 时间复杂度: O(n)
- 空间复杂度: O(1)
7. ⾯试题 02.07. 链表相交
思路
求两个链表交点节点的指针。 这⾥同学们要注意,交点不是数值相等,⽽是指针相等

求出两个链表的⻓度,并求出两个链表⻓度的差值,然后让curA移动到,和curB 末尾对⻬的位置

依次比较指针是否相同
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
lenA, lenB = 0, 0
cur = headA
while cur: # 求链表A的长度
cur = cur.next
lenA += 1
cur = headB
while cur: # 求链表B的长度
cur = cur.next
lenB += 1
curA, curB = headA, headB
if lenA > lenB: # 让curB为最长链表的头,lenB为其长度
curA, curB = curB, curA
lenA, lenB = lenB, lenA
for _ in range(lenB - lenA): # 让curA和curB在同一起点上(末尾位置对齐)
curB = curB.next
while curA: # 遍历curA 和 curB,遇到相同则直接返回
if curA == curB:
return curA
else:
curA = curA.next
curB = curB.next
return None
8. 环形链表II
力扣题目链接
把环形链表讲清楚! 如何判断环形链表?如何找到环形链表的入口? LeetCode:142.环形链表II
思路
快慢指针法,分别定义 fast 和 slow 指针,从头结点出发, fast指针每次移动两个节点, slow指针每次移动⼀个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环
当两个指针进入环后,相当于fast在以一格的速度追slow
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# If there is a cycle, the slow and fast pointers will eventually meet
if slow == fast:
# Move one of the pointers back to the start of the list
slow = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow
# If there is no cycle, return None
return None
找环入口


相遇节点就是入口
总结:链表基础与操作
链表是一种通过指针连接的线性数据结构,每个节点包含数据域和指针域。根据指针的数量和连接方式,链表有多种类型,如单链表、双链表和循环链表。
基础知识
- 节点结构:每个节点由数据和指针组成,指向下一个节点,最后一个节点指向
null。 - 链表类型:
- 单链表:每个节点有一个指针指向下一个节点。
- 双链表:每个节点有两个指针,分别指向前一个和后一个节点。
- 循环链表:最后一个节点指向头节点,形成一个环。
链表的操作
- 添加节点:在链表的头部、尾部或指定位置添加新节点,时间复杂度为 O(1)。
- 删除节点:可以通过更新指针实现,时间复杂度为 O(n)(查找节点时)。
链表常见问题
-
移除链表元素:力扣题目链接。
- 使用虚拟头结点简化删除操作,保证时间和空间复杂度均为 O(n) 和 O(1)。
-
设计链表:力扣题目链接。
- 实现基本的链表操作,如获取、添加、删除节点等。
-
反转链表:力扣题目链接。
- 使用双指针法或递归法对链表进行反转。
-
删除链表倒数第 N 个节点:力扣题目链接。
- 采用双指针法,先移动快指针 n 步,再一起移动,找到并删除目标节点。
-
链表相交:力扣题目链接。
- 通过比较两链表的长度,调整起始位置,然后逐步查找相交节点。
-
环形链表:力扣题目链接。
- 使用快慢指针法判断链表是否有环,并找到环的入口。
总结
掌握链表的基本概念及其操作是数据结构中的重要环节,通过实际问题的练习,能够提升对链表结构的理解和应用能力。通过各种算法题的练习,对链表的操作和应用可以更加游刃有余。

被折叠的 条评论
为什么被折叠?



