北大硕士LeetCode算法专题课--链表相关问题

文章介绍了链表的基础知识,包括其数据结构特性、与数组的区别,以及如何在链表中添加、删除节点。文章提供了Python实现链表的示例,并讨论了链表操作的时间复杂度。此外,还提到了其他链表相关的算法问题,如反转链表、删除元素等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

算法面试相关专题:

北大硕士LeetCode算法专题课-字符串相关问题_

北大硕士LeetCode算法专题课-数组相关问题_

北大硕士LeetCode算法专题课-基础算法查找_

北大硕士LeetCode算法专题课-基础算法之排序_

北大硕士LeetCode算法专题课---算法复杂度介绍_

北大硕士LeetCode算法专题课-查找相关问题_

链表的概

链表Linked list是一种常见的基础数据结,是由一组不必相连【不必相连:可以连续也可以不连续】的内存结构 【节点】,  按特定的顺序链接在一起的抽象数据类型。

链表中的元素称为节点(node),每一个节点都有两个属性

一个用来记录当前节点中保存的数据

一个用来记录下一个节点的引用

链表是节点的集合。第一个节点(Node)一般被称为Head。最后一个节点的Next属性必须指向 None ,表明是链表的结尾

 链表和数组的区

在大多数编程语言中,链表和数组在内存中的存储方式存在明显差异。数组要求内存空间是连续的,链表可以不连续。 然而,在 Python list是动态数组。所以在Python中列表和链表的内存使用非常相似。

链表和数组在以下的操作中也有本质区别:

插入元素:数组中插入元素时,插入位置之后的所有元素都需要往后移动一位,所以数组中插入元素最坏时间复杂度是 O(n)链表可以达到 O(1) 的时间复杂度

删除元素:数组需要将删除位置之后的元素全部往前移动一位,最坏时间复杂度是 O(n)链表可以达到 O(1) 的时间复杂度

随机访问元素:数组可以通过下标直接访问元素,时间复杂度为O(1)。链表需要从头结点开始遍历,时间复杂度为O(n)

获取长度: 数组获取长度的时间复杂度为O(1),链表获取长度也只能从头开始遍历,时间复杂度为O(n)

实现链

先创建一个类,LinkedList

class LinkedList:
def 	init 	(self):
self.head = None

 LinkedList中,需要存储的唯一信息是链表的开始位置(链表的头部)。接下来,创建另一个类Node来表示链表的 每个节点

class Node:
def 	init 	(self, data):  self.data = data  self.next = None

我们可以给刚创建的两个类添加   repr   方法, 在创建实例的时候输出更多有用的信息

class LinkedList:
def 	init 	(self):
self.head = None

def 	repr 	(self):  node = self.head  nodes = []
while node is not None:  nodes.append(node.data)  node = node.next
nodes.append("None")
return " -> ".join(nodes)
class Node:
def 	init 	(self, data):  self.data = data  self.next = None

def 	repr 	(self):
return self.data

创建测试类, 测试上面的代码

from LinkedList import LinkedList
from Node import Node

if  		name 	 == ' 	main 	':  llist = LinkedList()  print(llist)

first_node = Node('a')  llist.head = first_node  print(llist)

second_node = Node('b')  third_node = Node('c')  first_node.next = second_node  second_node.next = third_node  print(llist)

 

修改   init   方法,可以传入列表快速创建LinkedList

def 	init 	(self, nodes=None):
self.head = None
if nodes is not None:
node = Node(data=nodes.pop(0))
self.head = node
for elem in nodes:
node.next = Node(data=elem)  node = node.next

 实现链表,添加节点

在头部添加Node:在链表的开头添加一个Node,不必遍历链表,只需将新的Nodenext属性指向 self.head ,  并将新的node设置为新的 self.head

 在尾部添加Node:必须遍历链表,与list不同list可以直接获取长度, 链表只有从第一个Node,不断的去获取下一个Node  才能知道链表的尾部

def add_last(self, node):
if self.head is None:  self.head = node  return
for current_node in self:  pass
current_node.next = node

在指定元素后添加Node:遍历链表找到目标Node 把目标Node的下一个元素, 赋值给要添加Nodenext属性, 然后修改 目标Nodenext属性, 指向新添加的Node, 当链表为空以及目标元素不存在时抛出异常

def add_after(self, target_node_data, new_node):
if self.head is None:
raise Exception("List is empty")

for node in self:
if node.data == target_node_data:  new_node.next = node.next  node.next = new_node
return

raise Exception("Node with data '%s' not found" % target_node_data)

在指定元素前添加Node:遍历链表找到目标Node,还需要记录当前节点的前一个节点。

def add_before(self, target_node_data, new_node):
if self.head is None:
raise Exception("List is empty")

if self.head.data == target_node_data:
return self.add_first(new_node)

prev_node = self.head  for node in self:
if node.data == target_node_data:  prev_node.next = new_node  new_node.next = node
return
prev_node = node

raise Exception("Node with data '%s' not found" % target_node_data)

实现链表,删除节

删除Node:遍历链表找到目标Node,将目标Node的前一个Nodenext属性,指向目标Nodenext节点

def remove_node(self, target_node_data):
if self.head is None:
raise Exception("List is empty")

if self.head.data == target_node_data:
self.head = self.head.next  return

previous_node = self.head  for node in self:
if node.data == target_node_data:  previous_node.next = node.next  return
previous_node = node

raise Exception("Node with data '%s' not found" % target_node_data)

其它常见链表介

刚才实现的是单链表,常见链表除了单链表之外还有双向链表以及循环链表

双向链表,顾名思义,Node中除了记录下一个元素next之外, 还通过一个属性记录上一个元素

刚才实现的是单链表,常见链表除了单链表之外还有双向链表以及循环链表

循环链表,将单链表中最后一个Nodenext 属性, 指向头节点(head),即为循环链表

反转链表(LeetCode 206)

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表

示例:

 

 

移除链表元素(LeetCode 203

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,  并返回 新的头节点

示例1:

由于链表的头节点 head 有可能需要被删除,因此创建哑节点 dummyHead dummyHead.next=head初始化 temp=dummyHead然后遍历链表进行删除操作。最终返回 dummyHead.next即为删除操作后的头节点。

def removeElements(head, val):  dummy_head = Node(0)  dummy_head.next = head  temp = dummy_head
while temp.next:
if temp.next.data == val:  temp.next = temp.next.next
else:
temp = temp.next
return dummy_head.next

复杂度分析 时间复杂度:O(n),其中 n 是链表的长度。需要遍历链表一次。 空间复杂度:O(1)

两两交换链表中的节点(LeetCode 24)

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本 题(即,只能进行节点交换)。

示例 1:

 

 

删除链表中的节点(LeetCode 237)

给请编写一个函数,用于 删除单链表中某个特定节点 。在设计函数时需要注意,你无法访问链表的头节点 head ,只 能直接访问 要被删除的节点

题目数据保证需要删除的节点 不是末尾节点

 删除链表中的节点(LeetCode 237)

思路分析:我们在做链表元素删除时, 一般的思路是,

找到要删除节点的上一个节点

将上一个节点的next属性 指向要删除节点的下一个节

 但是,当前题目中的要求是, 我们无法访问头结点, 那么我们就无法得知要删除节点的上一个节点,但是我们能够获 取要删除节点的下一个节点, 利用它来完成我们的需求

[4, 5,1, 9] 链表,删除节点 5。既然要删除5 我们把5的下一个节点的值付给它,然后删除下一个节点

删除链表中的倒数第N个节点(LeetCode 19)

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

输入:head = [1,2,3,4,5], n = 2

输出:[1,2,3,5]

删除链表中的倒数第N个节点(LeetCode 19)

思路: 首先遍历获取长度,创建一个虚拟头结点,从虚拟头结点开始遍历, Ln+1 个节点。当遍历到第 Ln+1个节 点时,它的下一个节点就是我们需要删除的节点

def removeNthFromEnd(head, n):
def getLength(head):  length = 0
while head:
length += 1
head = head.next
return length

dummy = Node(0)  dummy.next = head
length = getLength(head)  cur = dummy
for i in range(1, length - n + 1):  cur = cur.next
cur.next = cur.next.next
return dummy.next

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值