详解链表相关算法

详解链表相关算法

文章目录

  • 详解链表相关算法
    • 概念
      • 链表操作
        • 经典例题
      • Tips
      • 种类
    • 通用方法
      • 数组转化成链表
      • 链表找中点
      • 合并两个有序链表
      • 链表排序(leetcode-148)
    • 常见题型
      • leetcode-23-Merge k Sorted Lists
      • leetcode-[147-Insertion Sort List](https://leetcode.cn/problems/insertion-sort-list/)
      • leetcode-[355. Design Twitter](https://leetcode.cn/problems/design-twitter/)

概念

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

你可能感兴趣的:(Algorithm,链表,算法,数据结构)