本次博客我是通过Notion软件写的,转md文件可能不太美观,大家可以去我的博客中查看:北天的 BLOG,持续更新中,另外这是我创建的编程学习小组频道,想一起学习的朋友可以一起!!!
链表是一种特殊的数据结构,它是由节点(node)组成的。每个节点都存储了一个数据元素和一个指向下一个节点的指针。指向最后一个节点的指针是空的。因此,我们可以从第一个节点开始,通过沿着指针移动到下一个节点,直到到达最后一个节点为止。
链表有两种类型:单向链表和双向链表。在单向链表中,每个节点只指向下一个节点,而在双向链表中,每个节点都指向下一个节点和前一个节点。
链表具有动态内存分配,因此我们可以在不需要预先知道链表大小的情况下在链表中插入或删除元素。
在 Python 中,我们可以使用类来实现链表。每个节点可以是一个类的实例,其中包含数据元素和指向下一个节点的指针。
操作链表的常见任务包括插入节点、删除节点、查找节点、遍历链表等。
常用的链表操作有单链表、双向链表、循环链表、双向循环链表等。
下面我们来简单的介绍一下单链表、双向链表、循环链表、双向循环链表:
插入:在链表的任意位置插入新节点
# 节点类
class Node:
def __init__(self, data):
self.data = data
self.next = None
# 链表类
class LinkedList:
def __init__(self):
self.head = None
# 在指定位置插入一个新节点
def insert_at_pos(self, pos, data):
# 创建一个节点
new_node = Node(data)
# 如果链表为空
if self.head is None:
self.head = new_node
return
# 如果位置为0,则在开头插入
if pos == 0:
new_node.next = self.head
self.head = new_node
return
# 遍历链表找到位置
temp = self.head
count = 0
while temp is not None:
if count == pos - 1:
break
temp = temp.next
count += 1
# 插入节点
new_node.next = temp.next
temp.next = new_node
删除:从链表中删除指定的节点
# 节点类
class Node:
def __init__(self, data):
self.data = data
self.next = None
# 链表类
class LinkedList:
def __init__(self):
self.head = None
# 删除指定数据的节点
def delete_node(self, data):
# 如果链表为空
if self.head is None:
return
# 如果头节点有数据
if self.head.data == data:
self.head = self.head.next
return
# 遍历链表
temp = self.head
while temp.next is not None:
if temp.next.data == data:
break
temp = temp.next
# 删除节点
if temp.next is not None:
temp.next = temp.next.next
查询:查询链表中指定元素的位置
# 节点类
class Node:
def __init__(self, data):
self.data = data
self.next = None
# 链表类
class LinkedList:
def __init__(self):
self.head = None
# 找到指定数据的位置
def find_pos(self, data):
# 如果链表为空
if self.head is None:
return -1
# 遍历链表
temp = self.head
count = 0
while temp is not None:
if temp.data == data:
return count
temp = temp.next
count += 1
# 元素未找到
return -1
修改:修改链表中指定元素的值
# 节点类
class Node:
def __init__(self, data):
self.data = data
self.next = None
# 链表类
class LinkedList:
def __init__(self):
self.head = None
# 更新指定位置的节点
def update_at_pos(self, pos, data):
# 如果链表为空
if self.head is None:
return
#遍历链表找到位置
temp = self.head
count = 0
while temp is not None:
if count == pos:
break
temp = temp.next
count += 1
#更新节点
if temp is not None:
temp.data = data
遍历:从头到尾遍历整个链表
# 节点类
class Node:
def __init__(self, data):
self.data = data
self.next = None
# 链表类
class LinkedList:
def __init__(self):
self.head = None
# 遍历链表
def traverse(self):
temp = self.head
while temp is not None:
print(temp.data)
temp = temp.next
计数:计算链表中节点的数量
# 节点类
class Node:
def __init__(self, data):
self.data = data
self.next = None
# 链表类
class LinkedList:
def __init__(self):
self.head = None
# 统计链表中节点的个数
def count_nodes(self):
temp = self.head
count = 0
while temp is not None:
count += 1
temp = temp.next
return count
排序:对链表中的元素进行排序
# 节点类
class Node:
def __init__(self, data):
self.data = data
self.next = None
# 链表类
class LinkedList:
def __init__(self):
self.head = None
# 对链表进行排序
def sort_linked_list(self):
temp = self.head
while temp is not None:
current = temp
while current.next is not None:
if current.data > current.next.data:
current.data, current.next.data = current.next.data, current.data
current = current.next
temp = temp.next
反转:将链表中的元素反转
# 节点类
class Node:
def __init__(self, data):
self.data = data
self.next = None
# 链表类
class LinkedList:
def __init__(self):
self.head = None
# 反转链表
def reverse(self):
prev = None
current = self.head
while current is not None:
next = current.next
current.next = prev
prev = current
current = next
self.head = prev
示例1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
示例2:
输入:head = [], val = 1
输出:[]
示例3:
输入:head = [7,7,7,7], val = 7
输出:[]
提示:
列表中的节点数目在范围 [0, 104] 内
1 <= Node.val <= 50
0 <= val <= 50
那么什么是虚拟节点呢?
虚拟节点(dummy node)是在链表头部创建一个不存储任何数据的特殊节点,用来解决链表操作问题。通常情况下,在链表头部插入或移除元素时,需要特殊处理,以避免对链表头部的直接操作。使用虚拟节点,可以在链表头部插入和移除元素时统一处理,从而简化代码。
# 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: ListNode, val: int) -> ListNode:
dummy = ListNode(0)
dummy.next = head
prev = dummy
current = head
while current:
if current.val == val:
prev.next = current.next
else:
prev = current
current = current.next
return dummy.next
上述代码的具体实现思路如下:
如何要对上述代码进行示例测试的话,需要注意这跟之前的数组不一样,具体例子如下:
head = ListNode(1)
head.next = ListNode(2)
head.next.next = ListNode(6)
head.next.next.next = ListNode(3)
head.next.next.next.next = ListNode(4)
head.next.next.next.next.next = ListNode(5)
head.next.next.next.next.next.next = ListNode(6)
a = Solution()
result = a.removeElements(head, 6)
node = result
while node:
print(node.val, end=" ")
node = node.next
B站上面卡尔老师对于这道题的思路讲得很不错,在这里推荐大家去看看他的教学视频:
https://www.bilibili.com/video/BV18B4y1s7R9/?spm_id_from=333.788&vd_source=eae0406d86828f39f6ac3a3b0e8a2a34
在链表类中实现这些功能:
示例:
MyLinkedList linkedList = new MyLinkedList();
linkedList.addAtHead(1);
linkedList.addAtTail(3);
linkedList.addAtIndex(1,2); //链表变为1-> 2-> 3
linkedList.get(1); //返回2
linkedList.deleteAtIndex(1); //现在链表是1-> 3
linkedList.get(1); //返回3
提示:
0 <= index, val <= 1000
请不要使用内置的 LinkedList 库。
get, addAtHead, addAtTail, addAtIndex 和 deleteAtIndex 的操作次数不超过 2000。
其中get(index)实现思路是:
其中addAtHead(val)实现思路是:
其中addAtTail(val)实现思路是:
其中addAtIndex实现思路是:
其中deleteAtIndex(index)实现思路是:
class Node(object):
def __init__(self, x=0):
self.val = x
self.next = None
class MyLinkedList(object):
def __init__(self):
self.head = Node()
self.size = 0 # 设置一个链表长度的属性,便于后续操作,注意每次增和删的时候都要更新
def get(self, index):
"""
:type index: int
:rtype: int
"""
if index < 0 or index >= self.size:
return -1
cur = self.head.next
while(index):
cur = cur.next
index -= 1
return cur.val
def addAtHead(self, val):
"""
:type val: int
:rtype: None
"""
new_node = Node(val)
new_node.next = self.head.next
self.head.next = new_node
self.size += 1
def addAtTail(self, val):
"""
:type val: int
:rtype: None
"""
new_node = Node(val)
cur = self.head
while(cur.next):
cur = cur.next
cur.next = new_node
self.size += 1
def addAtIndex(self, index, val):
"""
:type index: int
:type val: int
:rtype: None
"""
if index < 0:
self.addAtHead(val)
return
elif index == self.size:
self.addAtTail(val)
return
elif index > self.size:
return
node = Node(val)
pre = self.head
while(index):
pre = pre.next
index -= 1
node.next = pre.next
pre.next = node
self.size += 1
def deleteAtIndex(self, index):
"""
:type index: int
:rtype: None
"""
if index < 0 or index >= self.size:
return
pre = self.head
while(index):
pre = pre.next
index -= 1
pre.next = pre.next.next
self.size -= 1
# 双链表
# 相对于单链表, Node新增了prev属性
class Node:
def __init__(self, val):
# 存储节点的值
self.val = val
# 指向前一个节点
self.prev = None
# 指向下一个节点
self.next = None
# 双链表的实现
class MyLinkedList:
def __init__(self):
# 虚拟节点,存储的值可以是0或者任意值
self._head, self._tail = Node(0), Node(0)
# 虚拟节点的前一个节点和后一个节点都是自己
self._head.next, self._tail.prev = self._tail, self._head
# 添加的节点数
self._count = 0
def _get_node(self, index: int) -> Node:
# 当index小于_count//2时, 使用_head查找更快, 反之_tail更快
if index >= self._count // 2:
# 使用prev往前找
node = self._tail
for _ in range(self._count - index):
node = node.prev
else:
# 使用next往后找
node = self._head
for _ in range(index + 1):
node = node.next
# 返回找到的节点
return node
def get(self, index: int) -> int:
"""
获取链表中第 index 个节点的值。
如果索引无效,则返回 -1。
"""
# 如果index是合法的,即在0和_count-1的范围内
if 0 <= index < self._count:
# 找到对应的节点
node = self._get_node(index)
# 返回该节点的值
return node.val
else:
# 如果index不合法,返回-1
return -1
def addAtHead(self, val: int) -> None:
"""
在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
"""
# 调用_update方法,并传入链表的头节点,头节点的下一个节点,以及要插入的值val
self._update(self._head, self._head.next, val)
def addAtTail(self, val: int) -> None:
"""
将值为 val 的节点附加到链表的最后一个元素。
"""
# 调用_update方法,并传入链表的尾节点的前一个节点,尾节点,以及要插入的值val
self._update(self._tail.prev, self._tail, val)
def addAtIndex(self, index: int, val: int) -> None:
"""
在链表的第 index 个节点之前添加一个值为 val 的节点。如果索引等于链表的长度,则该节点将追加到链表的末尾。如果索引大于长度,则不会插入该节点。
"""
# 如果索引小于0,则将索引设置为0;如果索引大于链表长度,则直接返回
if index < 0:
index = 0
elif index > self._count:
return
# 获取索引对应的节点
node = self._get_node(index)
# 调用_update方法,并传入该节点的前一个节点,该节点,以及要插入的值val
self._update(node.prev, node, val)
def _update(self, prev: Node, next: Node, val: int) -> None:
"""
更新节点
:param prev: 相对于更新的前一个节点
:param next: 相对于更新的后一个节点
:param val: 要添加的节点值
"""
# 计数累加
self._count += 1
# 创建一个新节点
node = Node(val)
# 设置前后节点的指向
prev.next, next.prev = node, node
node.prev, node.next = prev, next
def deleteAtIndex(self, index: int) -> None:
"""
如果索引有效,则删除链表中的第 index 个节点。
"""
# 如果 index 有效
if 0 <= index < self._count:
# 获取 index 节点
node = self._get_node(index)
# 计数-1
self._count -= 1
# 将前后节点的指向跳过该节点
node.prev.next, node.next.prev = node.next, node.prev
B站上面卡尔老师对于这道题的思路讲得很不错,在这里推荐大家去看看他的教学视频:
https://www.bilibili.com/video/BV1FU4y1X7WD/?spm_id_from=333.788&vd_source=eae0406d86828f39f6ac3a3b0e8a2a34
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
提示:
链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000
递归的代码实现会非常的简洁,但是有时候会很难理解每一步的含义,所以我们可以先尝试使用第二种循环的方法,也就是使用双指针的方法反转链表。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
prev = None
curr = head
while curr:
next_node = curr.next
curr.next = prev
prev = curr
curr = next_node
return prev
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseList(self, head: ListNode) -> 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
我们还可以在原函数reverseList里面自己定义一个反转函数实现反转操作,具体实现过程如下:
# 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:
"""
反转单链表
:param head: 链表的头节点
:return: 反轮后的链表头节点
"""
def reverse(pre, cur):
"""
递归函数,每次传入前一个节点和当前节点,反转链表
:param pre: 前一个节点
:param cur: 当前节点
:return: 反转后的链表头节点
"""
if not cur:
# 当前节点为空,说明已经到达末尾,返回前一个节点
return pre
# 保存当前节点的下一个节点
tmp = cur.next
# 反转链表,当前节点的下一个节点指向前一个节点
cur.next = pre
# 递归下一个节点
return reverse(cur, tmp)
# 从head开始,pre为None
return reverse(None, head)
B站上面卡尔老师对于这道题的思路讲得很不错,在这里推荐大家去看看他的教学视频:
https://www.bilibili.com/video/BV1nB4y1i7eL/?spm_id_from=333.788&vd_source=eae0406d86828f39f6ac3a3b0e8a2a34
示例1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例2:
输入:head = []
输出:[]
示例3:
输入:head = [1]
输出:[1]
提示:
链表中节点的数目在范围 [0, 100] 内
0 <= Node.val <= 100
和上面的反转链表题目操作类似,我们还是先使用双指针迭代实现,然后再用递归的方法实现。
具体双指针代码实现如下:
# 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
dummy = ListNode(0)
dummy.next = head
pre = dummy
while pre.next and pre.next.next:
cur = pre.next
post = cur.next
cur.next = post.next
post.next = cur
pre.next = post
pre = pre.next.next
return dummy.next
使用虚拟节点递归实现的思路:
具体递归代码实现如下:
# 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
dummy = ListNode(0)
dummy.next = head
if head and head.next:
next_node = head.next.next
head.next.next = head
head.next = self.swapPairs(next_node)
dummy.next = head.next
return dummy.next
如果使用动态状态方程写的话可以更加简洁:
# 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
dummy = ListNode(0)
dummy.next = head
if head and head.next:
head.next, dummy.next.next, dummy.next = self.swapPairs(head.next.next), head, head.next
return dummy.next
B站上面卡尔老师对于这道题的思路讲得很不错,在这里推荐大家去看看他的教学视频:
https://www.bilibili.com/video/BV1YT411g7br/?spm_id_from=333.788&vd_source=eae0406d86828f39f6ac3a3b0e8a2a34
示例1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例2:
输入:head = [1], n = 1
输出:[]
示例3:
输入:head = [1,2], n = 1
输出:[1]
提示:
链表中结点的数目为 sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
具体代码实现:
# 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 = ListNode(0)
dummy.next = head
slow = dummy
fast = dummy
for i in range(n + 1):
fast = fast.next
while fast:
slow = slow.next
fast = fast.next
slow.next = slow.next.next
return dummy.next
我们还可以换一种写法来实现:
首先,我们新增一个虚拟节点 dummy,它的下一个节点是原链表的头节点 head。然后,我们定义两个指针 slow 和 fast,它们都从 dummy 出发,最开始 fast 指针先往前走 n 步。接着,两个指针同时向前走,当 fast 指针到达结尾后,slow 的下一个节点就是倒数第 N 个节点。最后,我们直接删除该节点,然后返回 dummy.next 即可。
# 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 = ListNode()
dummy.next = head
slow, fast = dummy, dummy
while(n!=0): #fast先往前走n步
fast = fast.next
n -= 1
while(fast.next!=None):
slow = slow.next
fast = fast.next
#fast 走到结尾后,slow的下一个节点为倒数第N个节点
slow.next = slow.next.next #删除
return dummy.next
B站上面卡尔老师对于这道题的思路讲得很不错,在这里推荐大家去看看他的教学视频:
https://www.bilibili.com/video/BV1vW4y1U7Gf/?spm_id_from=333.788
图示两个链表在节点 c1 开始相交:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SzDLGQGH-1676269812178)(https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/14/160_statement.png)]
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
自定义评测:
评测系统 的输入如下(你设计的程序 不适用 此输入):
intersectVal - 相交的起始节点的值。如果不存在相交节点,这一值为 0
listA - 第一个链表
listB - 第二个链表
skipA - 在 listA 中(从头节点开始)跳到交叉节点的节点数
skipB - 在 listB 中(从头节点开始)跳到交叉节点的节点数
评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA 和 headB 传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。
示例1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
— 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。
示例2:
输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [1,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。
提示:
listA 中节点数目为 m
listB 中节点数目为 n
1 <= m, n <= 3 * 104
1 <= Node.val <= 105
0 <= skipA <= m
0 <= skipB <= n
如果 listA 和 listB 没有交点,intersectVal 为 0
如果 listA 和 listB 有交点,intersectVal == listA[skipA] == listB[skipB]
进阶:你能否设计一个时间复杂度 O(m + n)
、仅用 O(1)
内存的解决方案?
创建两个指针pA和pB,分别指向两个链表的头结点headA和headB。
先判断两个链表是否存在交点:
a. 如果pA == pB,则返回pA,说明此时两个指针所指向的结点就是交点。
b. 如果pA != pB,则继续遍历:
i. 若pA存在,则将pA移动到pA.next,否则将pA指向headB;
ii. 若pB存在,则将pB移动到pB.next,否则将pB指向headA。
循环2的步骤,直到pA == pB或pA和pB均不存在,此时说明两个链表没有交点或已经找到了交点。
知道了具体实现思路,那么写出代码也就不是很难了,具体代码实现是:
# Definition for singly-linked list.
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
pA = headA
pB = headB
while pA != pB:
pA = pA.next if pA else headB
pB = pB.next if pB else headA
return pA
我们使用的这种方法的优点在于,不论两个链表的长度是否相等,最多只需要遍历两次链表,时间复杂度为O(m + n),其中m和n分别是两个链表的长度。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
提示:
链表中节点的数目范围在范围 [0, 104] 内
-105 <= Node.val <= 105
pos 的值为 -1 或者链表中的一个有效索引
进阶:你是否可以使用 O(1)
空间解决此题?
# 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:
fast = slow = head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
if fast == slow:
slow = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow
return None
上述算法的实现步骤为:
B站上面卡尔老师对于这道题的思路讲得很不错,在这里推荐大家去看看他的教学视频:
https://www.bilibili.com/video/BV1if4y1d7ob/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=eae0406d86828f39f6ac3a3b0e8a2a34