本系列是算法通关手册LeeCode的学习笔记
算法通关手册(LeetCode) | 算法通关手册(LeetCode) (itcharge.cn)
本系列为自用笔记,如有版权问题,请私聊我删除。
目录
一,链表简介
1. 链表(Linked List)
一种线性表数据结构,使用一组任意的存储单元来存储一组具有相同类型的数据。链表是实现线性表链式存储的基础。
以单链表为例:
链表通过一组任意的存储单元串联在一起,每个数据元素占用若干存储单元的组合称为一个链节点 。为了将所有节点串起来,每个链节点不仅要存放一个数据元素的值,还要存放一个指出这个数据元素在逻辑关系上的直接后继元素所在链节点的地址,该地址称为后继指针next
优点:存储空间不用事先分配,一些操作的时间效率远比数组高;
缺点:指针需要占用存储空间,空间开销大。
2. 双向链表(Doubly Linked List)
也叫双链表,每个链节点中有两个指针,分别指向直接后继和直接前驱。
特点:从任意一个节点开始,都可以访问前驱节点和后继节点。
3. 循环链表(Circular Linked List)
最后一个节点指向头节点,形成一个环。
特点:从循环链表的任何一个结点出发,都能找到任何其他节点。
二,链表基本操作
1. 链表的定义与建立
链表是由链节点通过 next 连接而成。
链节点类(ListNode):使用成员变量 val 表示数据元素的值,指针 next 表示后继指针。
链表类(LinkedList):使用一个链节点变量 head 来表示链表的头节点。
class ListNode:
def __init__(self, val = 0, next = None):
self.val = val
self.next = next
class LinkedList:
def __init__(self):
self.head = None
建立线性链表:根据线性表的数据元素动态生成链节点,并以此将其连接到链表中。
步骤:
依次获取表中的数据元素;
每获取一个,就为该数据元素生成一个新节点,将新节点插入到链表尾部;
插入完毕后,返回第一个链节点的地址。
示例代码:
class LinkedList:
def __init__(self):
self.head = None
def creat(self, data):
self.head = ListNode(0)
cur = self.head
for i in range(len(data)):
node = ListNode(data[i])
cur.next = node
cur = cur.next
时间复杂度依赖于数据 data 表的长度,所以为 O(n)
2. 获取链表长度
使用指针变量 cur 顺着链表 next 移动,并使用计数器 count 记录元素个数。
步骤:
指针变量指向第一个链节点;
顺着链节点的 next 遍历链表,每指向一个链节点, count 计数一次;
cur 指向为空时结束遍历,返回 count
示例代码:
def length(self):
cur = self.head
count = 0
while cur:
count += 1
cur = cur.next
return count
时间复杂度为 O(n)
3. 查找元素
在链表中查找值为 val 的元素:从头节点 head 开始,沿着链表节点逐一进行查找。若找到则返回节点地址,否则返回 None
步骤:
让指针变量 cur 指向链表的第一个链节点;
顺着链节点的 next 指针遍历链表,如果 cur.val == val ,返回 cur;
若 cur 为空仍未找到,返回 None。
示例代码:
def find(self, val):
cur = self.head
while cur:
if val == cur.val:
return cur
cur = cur.next
return None
时间复杂度为 O(n)
4. 插入元素
按照链表中插入元素的位置分为:链表头部、链表尾部、链表中间插入元素。
链表头部插入元素:在链表第一个节点之前插入值为 val 的链节点。
步骤:
创建一个值为 val 的链节点 node;
将 node 的后继指针指向链表头节点 head;
将链表头节点指向 node。
示例代码:
def insertFront(self, val):
node = ListNode(val)
node.next = self.head
self.head = node
时间复杂度为 O(1)
在链表尾部插入元素:在链表最后一个链节点之后插入值为 val 的链节点。
步骤:
创建一个值为 val 的链节点 node;
使用指针 cur 指向链表的头节点 head;
通过链节点的 next 指针移动 cur 直到 cur.next 为空;
将 cur.next 指向新的链节点 node。
示例代码:
def insertRear(self, val):
node = ListNode(val)
cur = self.head
while cur.next:
cur = cur.next
cur.next = node
时间复杂度为 O(n)
链表中间插入元素:在链表第 i 个链节点之前插入值为 val 的链节点。
步骤:
使用指针变量 cur 和一个计数器 count;
沿着链节点的 next 指针遍历链表,并计数;
遍历到 index - 1 时,创建一个值为 val 的节点 node;
将 node.next 指向 cur.next;
将 cur.next 指向 node。
示例代码:
def insertInside(self, val, index):
cur = self.head
count = 0
while count < index - 1:
cur = cur.next
count += 1
if not cur:
return 'Error'
node = ListNode(val)
node.next = cur.next
cur.next = node
时间复杂度为 O(n)
5. 改变元素
将链表中第 i 个元素值改为 val:首先要遍历到第 i 个链节点,然后更改元素值。
步骤:
使用指针变量 cur 和计数器 count;
沿着节点的 next 找到第 i 个链节点;
更改元素值。
示例代码:
def change(self, index, val):
count = 0
cur = self.head
while count < index and cur:
cur = cur.next
count += 1
if not cur:
return 'Error'
cur.val = val
时间复杂度为 O(n)
6. 删除元素
删除元素操作与插入元素一样,分为三种。
链表头部删除元素:删除链表的第一个节点。
步骤
将 self.head 沿 next 指针右移一步。
示例代码:
def removeFront(self):
if self.head:
self.head = self.head.next
时间复杂度为 O(1)
链表尾部删除元素:删除链表末尾最后一个链节点。
步骤:
先使用指针变量 cur 沿着 next 移动到倒数第二个链节点;
将此节点的 next 指针指向 None。
示例代码:
def removeRear(self):
if not self.head or not self.head.next:
return 'Error'
cur = self.head
while cur.next.next:
cur = cur.next
cur.next = None
时间复杂度为 O(n)
链表中间删除元素:删除链表第 i 个链节点。
步骤:
先使用指针变量 cur 移动到第 i - 1 个位置的链节点;
将 cur 的 next 指针指向第 i 个元素的下一个节点。
示例代码:
def removeInside(self, index):
count = 0
cur = self.head
while cur.next and count < index - 1:
cur = cur.next
count += 1
if not cur:
return 'Error'
del_node = cur.next
cur.next = del_node.next
时间复杂度为 O(n)
三,总结
链表是最基础、最简单的数据结构。是实现线性表的链式存储结构的基础。它使用一组任意的存储单元(可以是连续的,也可以是不连续的),来存储一组具有相同类型的数据。
链表最大的优点在于可以灵活的添加和删除元素。
在这里给出五道题来练练手
class ListNode:
def __init__(self, val):
self.val = val
self.next = None
class MyLinkedList:
def __init__(self):
self.head = ListNode(0)
self.size = 0
def get(self, index: int) -> int:
if 0 <= index < self.size:
cur = self.head
for _ in range(index + 1):
cur = cur.next
return cur.val
return -1
def addAtHead(self, val: int) -> None:
self.addAtIndex(0, val)
def addAtTail(self, val: int) -> None:
self.addAtIndex(self.size, val)
def addAtIndex(self, index: int, val: int) -> None:
if index > self.size:
return
elif index < 0:
index = 0
if 0 <= index <= self.size:
cur = self.head
for _ in range(index):
cur = cur.next
node = ListNode(val)
node.next = cur.next
cur.next = node
self.size += 1
def deleteAtIndex(self, index: int) -> None:
if 0 <= index < self.size:
cur = self.head
for _ in range(index):
cur = cur.next
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)
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head:
return head
cur = head
while cur.next :
if cur.val == cur.next.val:
if cur.next.next:
cur.next = cur.next.next
else:
cur.next = None
else:
cur = cur.next
return head
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head or not head.next:
return head
dummy = ListNode(0)
dummy.next = head
pre = dummy
cur = head
while cur:
while cur.next and cur.val == cur.next.val:
cur = cur.next
if pre.next == cur:
pre = pre.next
else:
pre.next = cur.next
cur = cur.next
return dummy.next
这里使用了哨兵节点 dummy 用于记录头节点的位置,以保证头节点被删除的情况下,仍可找到链表的开始位置。
# 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, pre = head, None
while cur:
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
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 reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]:
dummy = ListNode()
dummy.next = head
p0 = dummy
for _ in range(left - 1):
p0 = p0.next
pre = None
cur = p0.next
for _ in range(right - left + 1):
nxt = cur.next
cur.next = pre
pre = cur
cur = nxt
p0.next.next = cur
p0.next = pre
return dummy.next
算法通关手册(LeetCode) | 算法通关手册(LeetCode)
原文内容在这里,如有侵权,请联系我删除。