力扣148-排序链表

148. 排序链表 - 力扣(LeetCode)

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表

示例 1:

输入: head = 4,2,1,3

输出:1,2,3,4

示例 2:

输入: head = -1,5,3,4,0

输出:-1,0,3,4,5

示例 3:

输入: head = \[\]

输出:\[\]

提示:

  • 链表中节点的数目在范围 [0, 5 * 104]
  • -105 <= Node.val <= 105

进阶: 你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

方法一:归并排序(递归)

1.找到链表中间节点 head2 的前一个节点,并断开 head2 与其前一个节点的连接,这样链表就被分成了两段更短的链表,再对这两段链表递归调用 sortList. 得到排好序后的 head 和 head2 ,再合并两个有序链表

21. 合并两个有序链表 - 力扣(LeetCode)

找中间节点显然使用快慢指针,合并两个有序链表使用双指针

java 复制代码
class Solution {
    public ListNode sortList(ListNode head) {
        if(head == null || head.next == null) {
            return head;
        }
        // 找中间节点
        ListNode head2 = findMidNode(head);
        //分治
        head = sortList(head);
        head2 = sortList(head2);
        //合并
        return mergeTwoList(head, head2);
    }

    // 快慢指针找链表的中间节点
    public ListNode findMidNode(ListNode head) {
        ListNode pre = head; // 记录 slow 的前一个节点,方便后续断开
        ListNode slow = head;
        ListNode fast = head;
        while(fast != null && fast.next != null) {
            pre = slow;
            slow = slow.next;
            fast = fast.next.next;
        }
        pre.next = null; // 断开
        return slow; // 返回后半部分,因为前半部分的头节点已经知道是 head 了
    }

    // 双指针合并两个链表
    public ListNode mergeTwoList(ListNode head1, ListNode head2) {
        ListNode dummy = new ListNode();
        ListNode cur = dummy; // cur 指向新链表的末尾
        while(head1 != null && head2 != null) {
            if(head1.val < head2.val) {
                cur.next = head1;
                head1 = head1.next;
            } else {
                cur.next = head2;
                head2 = head2.next;
            }
            cur = cur.next;
        }
        cur.next = head1 != null ? head1 : head2; // 拼接剩余链表
        return dummy.next;
    }
}

递归深度为 O(logn),所以空间复杂度为 O(logn)

由于每层链表长度之和为 O(n),所以时间复杂度为 O(nlogn)

方法二:归并排序(迭代)

把方法一中的递归过程反向用迭代处理,省去递归的栈开销

递归方法中,我们是将链表分割为2份、4份、8份...直到每一份只有一个节点,再进行合并。现在我们自底向上处理:

1.归并长度为1的子链表:把第一个节点和第二个节点归并,放到链表末尾,再归并第三个节点和第四个节点,放到链表末尾......直到全部遍历完毕(因此需要事先直到链表长度),现在两两节点之间是有序的

2.归并长度为2的子链表:把第一组节点和第二组节点归并(每组两个节点),放到链表末尾,再归并第三组节点和第四组节点,放到链表末尾......直到全部遍历完毕,现在每四个节点之间是有序的

3.归并的子链表的长度是首项为1,公比为2的等比数列

4.直到归并的子链表的长度不小于链表长度

python 复制代码
class Solution:
    # 获取链表长度
    def getListLength(self, head : Optional[ListNode]) -> int :
        length = 0
        while head :
            length += 1
            head = head.next
        return length

    # 分割链表
    def splitList(self, head : Optional[ListNode], size : int) -> Optional[ListNode] :
        # 尝试找到下一组节点的头节点的前一个节点
        cur = head
        for _ in range(size - 1) : # 循环 size - 1 次,最终 cur 指向第 size 个节点(如果有)
            if cur is None :
                break
            cur = cur.next

        # 如果链表长度 <= size
        if cur is None or cur.next is None :
            return None # 不需要分割
        
        next_head = cur.next
        cur.next = None # 断开组与组之间的连接
        return next_head # 返回下一组的头节点,因为当前组的头节点已经知道是 head 了

    # 双指针合并两个有序链表,因为自底向上,所以要返回合并后链表的头节点和尾节点
    def mergeTwoLists(self, list1 : Optional[ListNode], list2 : Optional[ListNode]) -> Optional[ListNode] :
        cur = dummy = ListNode() # 哨兵 dummy, cur 指向新链表的尾节点
        while list1 and list2 :
            if list1.val < list2.val :
                cur.next = list1
                list1 = list1.next
            else :
                cur.next = list2
                list2 = list2.next
            cur = cur.next
        cur.next = list1 or list2 # 拼接剩余链表

        # 得到尾节点
        while cur.next :
            cur = cur.next
        return dummy.next, cur

    def sortList(self, head: Optional[ListNode]) -> Optional[ListNode] :
        length = self.getListLength(head) 
        dummy = ListNode(next = head) # 因为头节点可能变化,所以用虚拟头节点简化逻辑
        step = 1 # 归并的链表的长度
        while step < length :
            cur = dummy.next # 每轮循环的起始节点
            list_tail = dummy # 新链表的末尾
            while cur :
                # 从 cur 开始,分割出两段长为 step 的链表,归并后扔到链表末尾
                head1 = cur
                head2 = self.splitList(head1, step)
                cur = self.splitList(head2, step) # 下一轮循环的起始节点

                # 合并两段长为 step 的链表
                head, tail = self.mergeTwoLists(head1, head2)

                # 将合并后的头节点 head 放到当前链表尾节点的后面
                list_tail.next = head
                list_tail = tail # 现在 tail 是整个链表的末尾
            step *= 2
        return dummy.next
相关推荐
qeen879 小时前
【C++】vector的模拟实现详解(二)
c++·学习·算法·迭代器·stl
流浪0019 小时前
数据结构篇(四):线性表——链表——双链表
数据结构·链表
可编程芯片开发21 小时前
基于MATLAB的PEF湍流风场生成器模拟与仿真
算法
罗西的思考21 小时前
【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读笔记 --- (9)--- Reward Judging
人工智能·算法·机器学习
Mr.HeBoYan1 天前
一次持续三天才出现的丢包故障——深入解析 DPDK Memory Ordering、rte_ring 与 CPU Memory Barrier (下)
linux·网络·算法·架构·dpdk
wabs6661 天前
关于图论【深度优先搜索的理论基础】
算法·深度优先·图论
先吃饱再说1 天前
LeetCode 101. 对称二叉树
算法
是Dream呀1 天前
人眼看不出的伪造,AI如何识别?实测合合信息跨模态鉴伪与去反光技术
算法
QN1幻化引擎1 天前
SFA 信号场注意力:用8KB参数换248x KV Cache压缩,边缘设备也能跑长序列
人工智能·算法·gitee·gitlab