通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域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. 移除链表元素
删除元素就是让节点的 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. 设计链表
这里以单链表为例。注意,在定义链表之前需要先定义链表节点的结构体
(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. 反转链表
# 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
同样是当 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. 两两交换链表中的节点
几个重点:
(1)操作指针一定要指向要反转的两个节点的前一个结点,如要交换节点1和节点2,此时操作指针应指向dummy_head(节点1的前一个节点)
(2)遍历什么时候结束? while cur.next != None and cur.next.next != None:
(3)交换相邻两个元素时,要注意操作的先后顺序
# 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 个结点
要删某个节点,操作指针要指向该节点的前一个节点
如何找到倒数第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
注:交点指引用完全相同,即内存地址完全相同的交点
若一个指针到达链表的结尾,就切换到另一个链表的头节点继续移动
LeetCode刷题|python版本|160题|相交链表_哔哩哔哩_bilibili
分析:设第一个公共节点为node,链表A的节点数量为a,链表B的节点数量为b,两链表公共尾部的节点数量为c,则有
考虑构建两个节点指针 curA、curB,分别指向两链表头节点 headA、headB,做如下操作:
可以发现它们走的步数相同,所以此时指针 curA 和 curB 重合,且有两种情况:
故返回 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
算法复杂度:
这题有两小问:
# 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