链表(linked list)是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。算法导论链表相关的题解。
通常认为如果我们通过数组的下标读取数组中的数据,时间复杂度为 O(1),数组插入和删除元素的时间复杂度都是 O(N),但链表根据不同的情况会有不同的时间复杂度,因此链表这种数据结构通常适用于对效率要求较高的场景。如 redis 中的基本数据类型 list, hash, sorted set 都用到了链表这种数据结构。
操作 | 时间复杂度 | 备注 |
---|---|---|
数组查找元素 | O(1) | 通过下标寻找 |
数组插入元素 | O(N) | |
数组删除元素 | O(N) | |
链表查找元素(查找指定值) | O(N) | |
链表插入元素(头部插入元素) | O(1) | 不管是单链表还是双向链表 |
单链表插入元素(尾部插入元素) | O(N) | 如果通过 tail 记录尾部节点,每次插入元素的时候改变 tail 时间复杂度为 O(1) |
链表删除元素(删除指定值) | O(N) | 删除指定值 |
单链表删除某个节点(已知节点指针) | O(N) | 必须找到前驱节点 |
单链表删除头部节点 | O(1) | |
双向链表删除某个节点(已知节点指针) | O(1) | 因为已经知道前驱节点 |
leetcode-355. Design Twitter
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def __str__(self):
"""
当使用 print 的时候会调用该方法
:return:
"""
res = []
if self is not None:
idx = self
while idx is not None:
res.append(idx.val)
idx = idx.next
print(res)
return ','.join(list(map(str, res))) if len(res) > 0 else ''
def __len__(self):
"""
当调用 len() 方法时
:return:
"""
res = 0
if self is not None:
idx = self
while idx is not None:
idx = idx.next
res += 1
return res
def convert(nums: List[int]) -> Optional[ListNode]:
length = len(nums)
if length <= 0:
return None
head = ListNode(nums[0])
# tip: 指针必须直接等于 head, 不能是 head.next,否则就脱轨了
idx = head
i = 1
while i < length:
idx.next = ListNode(nums[i])
idx = idx.next
i += 1
return head
链表找中点:
# 方法 1
def split_list(head: Optional[ListNode]) -> (Optional[ListNode], Optional[ListNode]):
length = len(head)
i = 0
mid = length // 2
idx = head
while i < mid - 1:
idx = idx.next
i += 1
right = idx.next
idx.next = None
return head, right
# 方法 2
def split_list(head: Optional[ListNode]) -> (Optional[ListNode], Optional[ListNode]):
fast, slow = head, head
while fast is not None and fast.next is not None:
fast = fast.next.next
slow = slow.next
right = slow
idx = head
while idx.next != slow:
idx = idx.next
idx.next = None
return head, right
if __name__ == "__main__":
list = convert([-1, 5, 3, 4, 0])
res = split_list(list)
print('=====>', res[0]) # -1,5
print('=====>', res[1]) # 3,4,0
参考合并两个有序数组的做法,合并两个有序链表其实差不多,但区别在于不需要开辟额外空间存储节点
class Solution:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
"""
时间复杂度:O(M+N)
空间复杂度:O(1)
:param list1:
:param list2:
:return:
"""
head = ListNode('head')
h = head
i, j = list1, list2
while i is not None and j is not None:
if i.val > j.val:
h.next = j
j = j.next
else:
h.next = i
i = i.next
h = h.next
if i is not None:
h.next = i
if j is not None:
h.next = j
return head.next
根据排序算法的特点,尽量使用归并排序来解决链表排序问题。
class Solution:
def sortList_quick(self, head: Optional[ListNode]) -> Optional[ListNode]:
"""
使用原地快排来解决一下这个问题
时间复杂度最差为基准点正好是最大或最小值,O(N^2) 平均 O(NlogN)
空间复杂度:O(1)
:param head:
:return:
"""
def swap(i: Optional[ListNode], j: Optional[ListNode]):
i.val, j.val = j.val, i.val
def partition(start: Optional[ListNode], end: Optional[ListNode]):
# Tip 由于不能取到 end 所以 start==end start.next==end 的情况都要直接返回
if start == end or start.next == end:
return
pivot_val = start.val
pivot_idx = start
idx = start
first_idx = start
while first_idx != end:
# Tip 由于这里取不到 end, 所以在前面判断时不能让 start==end 或者 start.next 就是 end
if first_idx.val < pivot_val:
swap(first_idx, idx)
if pivot_idx == idx:
pivot_idx = first_idx
idx = idx.next
first_idx = first_idx.next
swap(idx, pivot_idx)
pivot_idx = idx
# Tip 取不到 end
partition(start, pivot_idx)
partition(pivot_idx.next, end)
partition(head, None)
return head
def sortList_merge(self, head: Optional[ListNode]) -> Optional[ListNode]:
"""
归并排序平均情况下时间复杂度都是 O(NlogN),所以能通过测试。
空间复杂度:O(logN) 合并时复杂度为常数,只需要递归的深度即可
:param head:
:return:
"""
def merge_sorted(head1: Optional[ListNode], head2: Optional[ListNode]) -> Optional[ListNode]:
"""
合并两个有序的链表
:param head1:
:param head2:
:return:
"""
head = ListNode("head")
res = head
start1 = head1
start2 = head2
while start1 is not None or start2 is not None:
if start1 is not None and start2 is not None:
if start1.val > start2.val:
res.next = start2
res = res.next
start2 = start2.next
else:
res.next = start1
res = res.next
start1 = start1.next
elif start1 is not None:
res.next = start1
break
else:
res.next = start2
break
return head.next
# get listnode length
if head is None or not head.next:
return head
split = split_list(head)
left_res = self.sortList(split[0])
right_res = self.sortList(split[1])
return merge_sorted(left_res, right_res)
以合并两个有序链表为基础解题,可以采用分治法解题
复杂度分析:
时间复杂度:考虑递归「向上回升」的过程——
第一轮合并 k / 2 k/2 k/2组链表,每一组的时间代价是 O ( 2 n ) O(2n) O(2n);
第二轮合并 k / 4 k/4 k/4组链表,每一组的时间代价是 O ( 4 n ) O(4n) O(4n)…
所以总的时间代价是 ∑ i = 1 l o g k k / 2 i ∗ 2 i ∗ n \sum_{i=1}^{logk}k/2^i*2^i*n ∑i=1logkk/2i∗2i∗n
故渐进时间复杂度为 O ( k n ∗ l o g k ) O(kn*logk) O(kn∗logk)。
空间复杂度:递归会使用到 O ( l o g k ) O(logk) O(logk) 空间代价的栈空间,合并两个有序链表用到的空间是常数级别的,递归的最大深度是 l o g k logk logk
class Solution:
def insertionSortList_0(self, head: Optional[ListNode]) -> Optional[ListNode]:
"""
了解了插入排序的实现,我们以 4->2->1->3 为例探索对链表进行插入排序
时间复杂度:O(N^2)
空间复杂度:O(N)
:param head:
:return:
"""
idx = head
idx = idx.next
res = ListNode(head.val)
while idx is not None:
idx_tmp = res
while idx_tmp.next is not None and idx_tmp.next.val < idx.val:
idx_tmp = idx_tmp.next
# 找到要插入的位置即为 idx_tmp.next
if idx_tmp.val > idx.val:
tmp = idx.next
idx.next = idx_tmp
res = idx
idx = tmp
else:
tmp0 = idx_tmp.next
idx_tmp.next = idx
tmp1 = idx.next
idx.next = tmp0
idx = tmp1
return res
def insertionSortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
"""
题解做法:维护一个 last 指针指向已排序的最后一个节点,并将其与待比较的元素比较,如果 last 比较小,则直接越过当前值即可,否则需要从头开始遍历。
同时保证比较过的元素不要再出现在循环中。
时间复杂度:O(N^2)
空间复杂度:O(1)
:param head:
:return:
"""
idx = head.next
last = head
while idx is not None:
if last.val <= idx.val:
last = last.next
else:
if head.val > idx.val:
last.next = idx.next
idx.next = head
head = idx
else:
idx_tmp = head
while idx_tmp.next.val < idx.val:
idx_tmp = idx_tmp.next
last.next = idx.next
idx.next = idx_tmp.next
idx_tmp.next = idx
idx = last.next
return head
很好的一个题目,既考察了系统设计也考察了对数据结构的理解
class ListNode:
def __init__(self, val: any):
self.val = val
self.next = None
class Twitter:
"""
使用数组来进行插入和删除的做法时间复杂度较高为 O(N),
所以可以使用链表这种数据结构,在向它的头部追加元素时时间复杂度为 O(1)
使用 set 集合来收集关注人,这样追加和删除的时间复杂度是 O(1)
"""
class Node:
def __init__(self):
self.followee = set()
self.tweet = None
def __init__(self):
self.recent = 10
self.user = {}
self.tweet_time = {}
def postTweet(self, userId: int, tweetId: int) -> None:
import time
if userId not in self.user:
self.user[userId] = Twitter.Node()
self.user[userId].followee.add(userId)
# 向链表头部追加一条 twitter,时间复杂度为 O(1)
tweet = ListNode(tweetId)
self.tweet_time[tweetId] = time.time()
if self.user[userId].tweet is None:
self.user[userId].tweet = tweet
else:
head = tweet
head.next = self.user[userId].tweet
self.user[userId].tweet = tweet
# 删掉多余的元素,时间复杂度为 O(1)
i = 0
idx = self.user[userId].tweet
while idx is not None and i < self.recent - 1:
idx = idx.next
i += 1
if i >= self.recent - 1:
if idx:
idx.next = None
def merge_linked_list(self, linkeds: List[ListNode]) -> ListNode:
"""
合并 n 个有序链表,时间复杂度:O(nklogn) k 是 10,n 是 followee 的个数
空间复杂度:O(klogn) logn 是递归的深度,k 是 10
:param linkeds:
:return:
"""
length = len(linkeds)
def merge(start: int, end: int) -> ListNode:
if start > end:
return None
res = ListNode('head')
if start == end:
return linkeds[start]
mid = (start + end) // 2
left = merge(start, mid)
right = merge(mid + 1, end)
res_idx = res
i = left
j = right
count = 0
# 只取 self.recent
while i is not None and j is not None:
if self.tweet_time[i.val] > self.tweet_time[j.val]:
res_idx.next = ListNode(i.val)
i = i.next
else:
res_idx.next = ListNode(j.val)
j = j.next
res_idx = res_idx.next
count += 1
if count >= self.recent:
break
while i is not None:
res_idx.next = ListNode(i.val)
res_idx = res_idx.next
i = i.next
count += 1
if count >= self.recent:
break
while j is not None:
res_idx.next = ListNode(j.val)
res_idx = res_idx.next
j = j.next
count += 1
if count >= self.recent:
break
# if i is not None:
# res_idx.next = i
# elif j is not None:
# res_idx.next = j
return res.next
return merge(0, length - 1)
def getNewsFeed(self, userId: int) -> List[int]:
follows = self.user[userId].followee if userId in self.user else set([userId])
feeds = []
todo = []
for val in follows:
if val in self.user:
if self.user[val].tweet is not None:
todo.append(self.user[val].tweet)
res = self.merge_linked_list(todo)
idx = res
i = 0
while i < self.recent and idx is not None:
feeds.append(idx.val)
idx = idx.next
i += 1
return feeds
def follow(self, followerId: int, followeeId: int) -> None:
"""
使用 set 集合,时间复杂度为 O(1)
:param followerId:
:param followeeId:
:return:
"""
if followerId not in self.user:
self.user[followerId] = Twitter.Node()
self.user[followerId].followee.add(followerId)
self.user[followerId].followee.add(followeeId)
def unfollow(self, followerId: int, followeeId: int) -> None:
"""
使用 set 集合,时间复杂度为 O(1)
:param followerId:
:param followeeId:
:return:
"""
if followerId in self.user:
# Tip 使用 discard 如果元素不存在也不会报错,remove 则会报错
self.user[followerId].followee.discard(followeeId)