LeetCode0148

本文详细解析了LeetCode148题目的解决方案,即如何使用非递归的归并排序算法对链表进行排序。算法采用迭代方式,通过不断合并固定长度的链表单元,逐步实现整体排序,时间复杂度为O(nlogn),空间复杂度为O(1)。文章通过图解和代码展示了整个过程。
摘要由CSDN通过智能技术生成
  1. LeetCode148 排序链表

  2. 题目描述:

  3. 代码:

    # Definition for singly-linked list.
    class ListNode:
        def __init__(self, val=0, next=None):
            self.val = val
            self.next = next
    class Solution:
        def sortList(self, head: ListNode) -> ListNode:
            # 思路:对于非递归的归并排序,需要使用迭代的方式替换cut环节从中点将链表断开. cut环节本质上是通过二分法得到链表最小节点单元,再通过多轮合并得到排序结果。每一轮合并merge操作针对的单元都有固定长度intv,例如: 第一轮合并时intv = 1,即将整个链表切分为多个长度为1的单元,并按顺序两两排序合并,合并完成的已排序单元长度为2。第二轮合并时intv = 2,即将整个链表切分为多个长度为2的单元,并按顺序两两排序合并,合并完成已排序单元长度为4。以此类推,直到单元长度intv >= 链表长度,代表已经排序完成。根据以上推论,我们可以仅根据intv计算每个单元边界,并完成链表的每轮排序合并,例如: 当intv = 1时,将链表第1和第2节点排序合并,第3和第4节点排序合并,……。
            # 算法:1. 统计链表长度length,用于通过判断intv < length判定是否完成排序;2. 额外声明一个节点res,作为头部后面接整个链表,用于:(A) intv *= 2即切换到下一轮合并时,可通过res.next找到链表头部h;(B) 执行排序合并时,需要一个辅助节点作为头部,而res则作为链表头部,排序合并时的辅助头部pre;后面的合并排序可以将上次合并排序的尾部tail用做辅助节点。在每轮intv下的合并流程: 1. 根据intv找到合并单元1和单元2的头部h1, h2。由于链表长度可能不是2^n,需要考虑边界条件:(A)在找h2过程中,如果链表剩余元素个数少于intv,则无需合并环节,直接break,执行下一轮合并;(B)若h2存在,但以h2为头部的剩余元素个数少于intv,也执行合并环节,h2单元的长度为c2 = intv - i。2. 合并长度为c1, c2的h1, h2链表,其中:(A) 合并完后,需要修改新的合并单元的尾部pre指针指向下一个合并单元头部h。(在寻找h1, h2环节中,h指针已经被移动到下一个单元头部)(B)合并单元尾部同时也作为下次合并的辅助头部pre。3. 当h == None,代表此轮intv合并完成,跳出。
    
            # intv代表当前合并的子列表的长度
            h, length, intv = head, 0, 1
            while h:
                h, length = h.next, length + 1
    
            # 额外声明一个节点res,作为头部后面接整个链表
            res = ListNode(0)
            res.next = head
    
            # 从intv = 1开始合并母列表。
            while intv < length:
                # pre是合并时的辅助头部,可变;res不变。
                pre, h = res, res.next
                while h:
                    # 得到当前未合并的子列表头部h1, h2
                    h1, i = h, intv
                    while i > 0 and h:
                        h, i = h.next, i - 1
                    if i > 0:
                        # i 代表当前合并排序的剩余长度。在这里,剩余长度不为零,而h是None,说明h2不存在,不需要合并了。
                        break
                    h2, i = h, intv
                    while i and h:
                        h, i = h.next, i - 1
                    # 需要分别维护c1, c2变量记录h1, h2长度,因为h2的长度可能小于intv
                    c1, c2 = intv, intv - i
                    while c1 > 0 and c2 > 0:
                        # pre.next总是取h1/h2中较小的。
                        if h1.val < h2.val:
                            pre.next, h1, c1 = h1, h1.next, c1 - 1
                        else:
                            pre.next, h2, c2 = h2, h2.next, c2 - 1
                        pre = pre.next
    
                    # 到这个位置,h1/h2长度相同的情况就处理完了,下面处理h1/h2长度不同的情况。
                    # 首先根据c1/c2得到剩余未处理的子列表。如果h1/h2长度相同,则c1/c2都等于0,这里取哪一个无所谓。
                    pre.next = h1 if c1 > 0 else h2
                    while c1 > 0 or c2 > 0:
                        # 这里同理,因为c1/c2同时减一,不需要判断谁大于0。又因为上一行pre.next已经指向未处理的子列表,也不用重新判断pre.next的指向.
                        pre, c1, c2 = pre.next, c1 - 1, c2 - 1
                    pre.next = h
                intv *= 2
    
            return res.next
    
  4. 此方法时间复杂度O(nlogn),空间复杂度O(1)。

  5. 图解:
    在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值