通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域data,另一个是指针域next(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针)
链接的入口点称为链表的头节点head
1、链表的类型
三种类型:单链表、双链表、循环链表
2、链表的存储方式
数组在内存中是连续分布的,但链表在内存中不是连续分布的,链表是通过指针域的指针来链接内存中各个节点的。即:链表的节点在内存中是分散存储的,通过指针连在一起
3、链表的定义
# Python定义链表的节点
class ListNode:
def __init__(self, val, next=None):
self.val = val
self.next = next
4、链表的操作
(1)删除节点:
此时节点D依然存留在内存中,只不过从链表中被移除了而已。Python有自己的内存回收机制,不用手动释放
(2 )添加节点:
链表的添加和删除都是时间复杂度为 O(1) 的操作,不会影响其他节点。但是,删除或者添加某个节点的前提是找到操作节点的前一个节点,而查找前一个节点的时间复杂度是 O(n)
5、性能分析
在定义数组的时候,长度是固定的,如果想改动数组的长度,则需要重新定义一个新的数组;
链表的长度可以是不固定的,并且可以动态增删,适合数据量不固定、频繁增删、较少查询的场景
203. 移除链表元素
203. 移除链表元素
删除元素就是让节点的 next 指针直接指向下一个节点的下一个节点,因为单链表的特殊性,所以需要找到操作节点的前一个节点
两种链表操作方式:
- 直接使用原来的链表执行删除操作:删除头节点和删除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来删除当前节点的,而头节点没有前一个节点,只要将头节点向后移动一位即可
- 设置一个虚拟头节点再执行删除操作:设置一个虚拟头节点dummy_head,这样原链表的所有节点都可以按照统一的方式删除了。新的头节点是dummy_head.next
# 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)
# 设置指针位置
cur = dummy_head
while cur.next != None:
if cur.next.val == val:
cur.next = cur.next.next
else:
cur = cur.next
return dummy_head.next
707. 设计链表
707. 设计链表
这里以单链表为例。注意,在定义链表之前需要先定义链表节点的结构体
(1)获取下标为 index 的节点的值:注意 cur 初始化为 dummy_head.next,也就是真正的head
(2)头部插入节点:注意操作顺序,十分重要!!!还要记得 size 要 +1
(3)尾部插入节点:记得 size 要 +1
(4)第n个节点前插入节点:操作第n个点,第n个点一定是cur_next,这样才能用 cur 去控制第n个点是增加还是删除。注意操作顺序,十分重要!!!以及记得 size 要 +1
(5)删除下标为 index 的节点:删除下标为 index 的节点(cur.next),必须要知道它的前一个节点(cur),让它的前一个节点指向该节点的后一个节点,才能删除该节点。以及记得 size 要 -1
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.dummy_head = ListNode(0)
self.size = 0
def get(self, index: int) -> int:
if index<0 or index>self.size-1:
return -1
cur = self.dummy_head.next
while index:
cur = cur.next
index-=1
return cur.val
def addAtHead(self, val: int) -> None:
new_node = ListNode(val)
new_node.next = self.dummy_head.next
self.dummy_head.next = new_node
self.size+=1
def addAtTail(self, val: int) -> None:
new_node = ListNode(val)
cur = self.dummy_head
while cur.next != None:
cur = cur.next
cur.next = new_node
self.size+=1
def addAtIndex(self, index: int, val: int) -> None:
if index>self.size:
return
else:
new_node = ListNode(val)
cur = self.dummy_head
while index:
cur = cur.next
index-=1
new_node.next = cur.next
cur.next = new_node
self.size+=1
def deleteAtIndex(self, index: int) -> None:
if index<0 or index>self.size-1:
return
cur = self.dummy_head
while index:
cur = cur.next
index-=1
cur.next = cur.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)
206. 反转链表
206. 反转链表
法1:双指针法
- cur 先指向 head,这是第一个指针
- 第二个指针,就是反转之后,cur指向它的后面了,定义为 pre(其实就是 cur 的前一位),初始化为空指针
- 遍历链表,直到 cur 指向空指针结束
- 遍历时,需要第三个指针 tmp,在没反转之前,保存 cur 的下一个指针,再反转
- 最后新链表的头节点就是 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: Optional[ListNode]) -> Optional[ListNode]:
cur = head
pre = None
while cur != None:
tmp = cur.next
cur.next = pre # 改变方向
# 指针往后遍历
pre = cur
cur = tmp
return pre
法2:递归法
同样是当 cur 为空的时候循环结束,不断将 cur 指向 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: Optional[ListNode]) -> Optional[ListNode]:
def revserse(cur, pre):
if cur == None:
return pre
tmp = cur.next
cur.next = pre
return revserse(tmp,cur)
return revserse(head, None)
24. 两两交换链表中的节点
24. 两两交换链表中的节点
几个重点:
(1)操作指针一定要指向要反转的两个节点的前一个结点,如要交换节点1和节点2,此时操作指针应指向dummy_head(节点1的前一个节点)
(2)遍历什么时候结束? while cur.next != None and cur.next.next != None:
- 链表节点个数为偶数时,cur.next = null
- 链表节点个数为奇数时,cur.next.next = null
- 链表节点个数为0,即空链表时,cur.next = null(此时cur指向虚拟头节点)
(3)交换相邻两个元素时,要注意操作的先后顺序
- 初始时,cur指向虚拟头节点
# 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]:
dummy_head = ListNode(next=head) # 创建虚拟头结点
cur = dummy_head # 当前操作指针指向虚拟头结点
while cur.next != None and cur.next.next != None:
tmp = cur.next # 保存节点1
tmp1 = cur.next.next.next # 保存节点3
cur.next = cur.next.next # 虚拟头结点指向节点2
cur.next.next = tmp # 节点2指向节点1
tmp.next = tmp1 # 节点1指向节点3
cur = cur.next.next # 将当前操作指针往后移动2位
return dummy_head.next # 返回交换后链表的头节点
19. 删除链表的倒数第N个结点
19. 删除链表的倒数第 N 个结点
要删某个节点,操作指针要指向该节点的前一个节点
如何找到倒数第N个节点?
- 虚拟头节点:好处是不需要对操作节点是不是头节点进行特殊的判断,而可以采用统一的方式进行删除 dummy_head = ListNode(next=head)
- 定义快慢指针,让快指针先移动N步,然后快慢指针再同时移动,直到快指针指向了空节点,此时慢指针就指向了要删的倒数第N个节点
- 但问题是,要删倒数第N个节点,操作指针要指向该节点的前一个节点,即慢指针应指向倒数第N个节点的前一个节点
- 故最终:定义快慢指针,让快指针先移动N+1步,然后快慢指针再同时移动,直到快指针指向了空节点,此时慢指针就指向了要删的倒数第N个节点的前一个
# 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: Optional[ListNode], n: int) -> Optional[ListNode]:
# 定义虚拟头节点
dummy_head = ListNode(next=head)
# 定义快慢指针,初始值都为虚拟头节点
fast = slow = dummy_head
# 让快指针先走n+1步
for i in range(n+1):
fast = fast.next
# 然后快慢指针再同时走,直到快指针为空
while fast != None:
fast = fast.next
slow = slow.next
# 此时慢指针指向倒数第n个节点的前一个
slow.next = slow.next.next
return dummy_head.next
160. 相交链表
160. 相交链表
注:交点指引用完全相同,即内存地址完全相同的交点
若一个指针到达链表的结尾,就切换到另一个链表的头节点继续移动
LeetCode刷题|python版本|160题|相交链表_哔哩哔哩_bilibili
分析:设第一个公共节点为node,链表A的节点数量为a,链表B的节点数量为b,两链表公共尾部的节点数量为c,则有
- 头节点 headA 到 node 前,共有 a - c 个节点
- 头节点 headB 到 node 前,共有 b - c 个节点
考虑构建两个节点指针 curA、curB,分别指向两链表头节点 headA、headB,做如下操作:
- 指针 curA 先遍历完链表A,再开始遍历链表B,当走到 node 时,共走步数为:a+(b-c)
- 指针 curB 先遍历完链表B,再开始遍历链表A,当走到 node 时,共走步数为:b+(a-c)
可以发现它们走的步数相同,所以此时指针 curA 和 curB 重合,且有两种情况:
- 若两链表有公共尾部(即c>0):指针 curA 和 curB 同时指向第一个公共节点node
- 若两链表无公共尾部(即c=0):指针 curA 和 curB 同时指向null
故返回 curA 和 curB 任一指针即可
# 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) -> Optional[ListNode]:
# 定义两指针,分别指向两链表的头节点
curA = headA
curB = headB
# 两指针不相等时就同时向后移动
while curA != curB:
curA = curA.next if curA else headB # 若curA走到空,跳到B
curB = curB.next if curB else headA
# 当两指针相等时返回任意一个指针即可
return curA
算法复杂度:
- 由于两指针最多遍历两个链表,故时间复杂度为O(m+n),其中m和n为两链表的长度
- 由于不需要开辟额外空间,故空间复杂度为O(1)
142. 环形链表II
142. 环形链表 II
这题有两小问:
- 判断链表是否有环:快慢指针都从头节点出发,快指针每次走2步,慢指针每次走1步,若能相遇则有环
- 寻找环的入口:在相遇节点处定义一个指针index1,在头节点处定一个指针index2,让index1和index2同时移动,每次移动一个节点,那么它们相遇的地方就是环的入口节点
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
# 定义快慢指针
fast, slow = head, head
while fast != None and fast.next != None: # 因为fast每次走两步
fast = fast.next.next
slow = slow.next
if fast == slow: # 若快慢指针相遇
index1 = fast # 相遇点
index2 = head # 头节点
while index1 != index2: # 两者相遇时终止
index1 = index1.next
index2 = index2.next
return index1
return None