详解链表相关算法

详解链表相关算法

概念

链表(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

Tips

  1. 链表指针必须指向 head 开始,这样才能修改指针的内容原链表也改动,否则就脱轨了
  2. 链表不要让后面的指针指向的位置等于前面的指针指向的位置,会出现环

种类

  • 单链表
  • 双向链表
  • 循环链表
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. 一可以通过遍历链表找到链表长度然后计算出一半的长度。
  2. 二可以通过快慢指针来实现
# 方法 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

链表排序(leetcode-148)

根据排序算法的特点,尽量使用归并排序来解决链表排序问题。

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)

常见题型

leetcode-23-Merge k Sorted Lists

以合并两个有序链表为基础解题,可以采用分治法解题

复杂度分析

时间复杂度:考虑递归「向上回升」的过程——

第一轮合并 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/2i2in

故渐进时间复杂度为 O ( k n ∗ l o g k ) O(kn*logk) O(knlogk)
空间复杂度:递归会使用到 O ( l o g k ) O(logk) O(logk) 空间代价的栈空间,合并两个有序链表用到的空间是常数级别的,递归的最大深度是 l o g k logk logk

leetcode-147-Insertion Sort List

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

leetcode-355. Design Twitter

很好的一个题目,既考察了系统设计也考察了对数据结构的理解

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)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值