链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的,一般用于插入与删除较为频繁的场景。
链表的每一个节点通过“指针”链接起来,每一个节点由数据(Data),指针(用来存储后一个节点的地址)组成,最开始的节点称为Head,最末尾节点的指针指向NULL。
下面我们讨论一下链表的访问,搜索,插入,删除等操作。
链表的访问与搜索:
由于链表的存储是非连续的,我们不能像数组那样采用首地址加上元偏移量计算出元素所在位置,只能挨个遍历直到找到某个节点,所以最坏的情况下我们要遍历N个元素,因此链表的访问时间复杂度为O(N)。搜索同理。
链表的访插入与删除:
同样由于链表的存储是非连续的,我们无需像数组一样改变所有元素的顺序,在插入与删除时,我们只需要修改前后元素的指针指向即可完成插入或删除操作。因此链表的插入与删除时间复杂度为O(1)。
在一定程度上,数组与链表是互补的。数组在访问和搜索上更便捷,因此更适用于读操作。链表在插入与删除上更便捷,因此更适用于写操作。我们应该根据实际情况来选择数据结构的使用。
下面是python中常用的链表操作。
# create an array
linkedlist = deque()
# add element time complexity:O(1)
linkedlist .append(1)
linkedlist .append(2)
linkedlist .append(3)
print(linkedlist) # [1,2,3]
# insert element time complexity:O(N)
linkedlist.insert(2,100)
print(linkedlist) # [1,2,100,3]
# access element time complexity:O(N)
temp = linkedlist [2]
print(temp) # 100
# search element time complexity:O(N)
index = linkedlist.index(100) # 2
# updata element time complexity:O(N)
linkedlist[2] = 99
print(linkedlist) # [1,2,99,3]
# remove element time complexity:O(N)
linkedlist.remove(99)
print(linkedlist) # [1,2,3]
# get size
size = len(linkedlist)
print(size) # 3
熟悉了链表的相关操作后我们来看看对于的简单题。力扣203移除链表元素:
对于这道题,我们首先要定义一个虚拟节点dummy来作为新的头结点以便保存完成删除操作后的链表。然后定义一个prev指针用来表示head的前一个节点,当head指向需要删除的val值时,prev直接指向head的下一个元素(从而把当前元素删除),当head没有指向val时,ore指向head,head指向head的下一个元素从而完成遍历。循环结束后链表中的val元素全部删除,返回由dummy保存的链表即可。
class Solution:
def removeElements(self, head: Optional[ListNode], val: int):
dummy = ListNode(0) # 设置一个变量储存整个链表
dummy.next = head # 指向链表的头
prev = dummy # 为了遍历链表保存的prev变量
while(head is not None):
if(head.val == val): # 当head指向需要删除的val时
prev.next = head.next # 指向下一个点即跳过这个数据
head = head.next # head也指向下一个完成遍历
else: # 当head不指向val时
prev = head # 保留该数据
head = head.next # head指向下一个完成遍历
return dummy.next # 返回删除元素后的链表
# time complexity:O(N) 遍历了一次链表
# space complexity:O(1) 没有产生新的数据结构
力扣206反转链表:
这道题首先我们首先定义prev指针指向链表的最后,curr指针指向链表的最开始位置,然后开始遍历链表,首先定义next指针保存curr的下一个位置以便curr往后完成遍历(如果直接反转指针curr没法按原来的顺序往下遍历),然后curr指针直接指向最后的prev(即第一个元素直接指向None变成了最后一个元素),然后prev移动到curr的位置,curr移动到next的位置(开始反转下一个元素),以此类推直到curr指向了None,此时prev(始终在curr的前一个)指向的是原来的最后一个元素(但是指针已经反转了所以他是链表的第一个元素),故可以结束循环返回prev即可。
class Solution:
def reverseList(self, head: Optional[ListNode]):
prev = None # prev指向None即链表末尾
curr = head # 初始化curr指向head
while curr is not None: # 遍历链表
next = curr.next # 定义下一个点以便往后遍历
curr.next = prev # 下一个点指向prev(即指向最后,成功把第一个元素放到最后)
prev = curr # prev移动到curr
curr = next # curr移动到开始定义的next以完成遍历
# 画图理解比较好理解
return prev # 此时prev指向原来的最后一个元素(None之前一个)
# 同时也是后来的第一个元素,因此返回prev
# time complexity:O(N) 遍历链表一次
# space complexity:O(1) 没有产生新的数据结构