题解 | #单链表的排序#

单链表的排序

http://www.nowcoder.com/practice/f23604257af94d939848729b1a5cda08

算法思想一:辅助数组

解题思路:

主要通过辅助数组实现链表的排序
1、遍历链表并将链表结点存储到数组 tmp 中
2、通过对 tmp 进行排序,实现链表结点的排序
3、构建新链表结点 result,遍历数组 tmp ,拼接新的返回链表
图解:

代码展示:

Python版本
class Solution:
    def sortInList(self , head ):
        # write code here
        tmp = []
        tmp.append(head.val)
        # 遍历链表存储到数组中
        while head.next:
            head = head.next
            tmp.append(head.val)
        # 数组排序
        tmp.sort()
        # 重新构造新链表结点
        result = ListNode(-1)
        temp = result
        # 遍历数组,将数组中元素添加到新的链表中
        for i in tmp:
            tt = ListNode(i)
            temp.next = tt
            temp = temp.next
        return result.next

复杂度分析

时间复杂度O(NlogN):N表示链表结点数量,遍历链表O(N),数组排序(NlogN),遍历数组O(N)
空间复杂度O(N):使用额外数组占用空间O(N)

算法思想二:归并排序(递归)

解题思路:

主要通过递归实现链表归并排序,有以下两个环节:
1、分割 cut 环节: 找到当前链表中点,并从中点将链表断开(以便在下次递归 cut 时,链表片段拥有正确边界);
    使用 fast,slow 快慢双指针法,奇数个节点找到中点,偶数个节点找到中心左边的节点。
    找到中点 slow 后,执行 slow.next = None 将链表切断。
    递归分割时,输入当前链表左端点 head 和中心节点 slow 的下一个节点 tmp(因为链表是从 slow 切断的)。
    cut 递归终止条件: 当head.next == None时,说明只有一个节点了,直接返回此节点
2、合并 merge 环节: 将两个排序链表合并,转化为一个排序链表。
    双指针法合并,建立辅助ListNode h 作为头部。
    设置两指针 left, right 分别指向两链表头部,比较两指针处节点值大小,由小到大加入合并链表头部,指针交替前进,直至添加完两个链表。
    返回辅助ListNode h 作为头部的下个节点 h.next。
    时间复杂度 O(l + r),l, r 分别代表两个链表长度。
3、特殊情况,当题目输入的 head == None 时,直接返回None。
图解:

代码展示:

JAVA版本
import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 * }
 */

public class Solution {
    /**
     * 
     * @param head ListNode类 the head node
     * @return ListNode类
     */
    public ListNode sortInList (ListNode head) {
        // write code here
        if (head == null || head.next == null)
            return head;
        // 使用快慢指针寻找链表的中点
        ListNode fast = head.next, slow = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        ListNode tmp = slow.next;
        slow.next = null;
        // 递归左右两边进行排序
        ListNode left = sortInList(head);
        ListNode right = sortInList(tmp);
        // 创建新的链表
        ListNode h = new ListNode(0);
        ListNode res = h;
        // 合并 left right两个链表
        while (left != null && right != null) {
            // left  right链表循环对比
            if (left.val < right.val) {
                h.next = left;
                left = left.next;
            } else {
                h.next = right;
                right = right.next;
            }
            h = h.next;
        }
        // 最后添加未对比的链表部分判断左链表是否为空
        h.next = left != null ? left : right;
        return res.next;
    }
}

复杂度分析

时间复杂度O(NlogN):N表示链表结点数量,二分归并算法O(NlogN)
空间复杂度O(1):仅使用常数级变量空间



全部评论
小白疑问:python里面head.val代表什么意思,为什么我想看排序后的tmp长什么样子,return tmp总会报错 'list' object has no attribute 'val',但是运行整个程序却能运行
1 回复 分享
发布于 2021-09-07 18:17
归并每一次递归都在创建新的节点,怎么是常数量级的空间复杂度?
6 回复 分享
发布于 2022-02-19 22:02
解法2中,空间复杂度不是o1吧,递归栈是on
1 回复 分享
发布于 2022-09-30 07:54 上海
讲的很明白 get归并排序
点赞 回复 分享
发布于 2021-08-31 19:27
学习了
点赞 回复 分享
发布于 2022-01-29 16:51
为什么一定要从中间断开呢,直接从第一个结点断开不行吗,反正递归终止条件都是只剩一个结点,相当于每两个结点之间都要断开,从哪断开不是一样的吗,求解
点赞 回复 分享
发布于 2022-03-08 11:56
为什么开始必须是fast = head.next,否则无限递归
点赞 回复 分享
发布于 2022-03-09 19:38
归并排序为什么内存超限
点赞 回复 分享
发布于 2022-03-14 20:11
请问【result = ListNode(-1)】和【tt = ListNode(i)】这两步分别是什么意义呀?是创建新链表表头和建立一个值为i的节点么?
点赞 回复 分享
发布于 2022-03-19 21:56
解法二,这不是O(1)啊,递归的空间复杂度是O(n)啊
点赞 回复 分享
发布于 2023-01-08 13:36 江苏
没人吗
点赞 回复 分享
发布于 2023-07-13 15:24 浙江
该算法的空间复杂度是O(logn),其中n是链表的长度。 在递归调用过程中,每次递归都会将链表分为两半,因此递归栈的深度为logn。每层递归需要额外的空间来存储中间节点的引用,而最多需要存储logn个中间节点。 因此,递归调用和存储中间节点所需的空间复杂度为O(logn)。除此之外,算法还需要创建一个新的链表来存储排序后的结果,所需的空间复杂度为O(n)。 综上所述,整个算法的空间复杂度为O(logn + n),即O(n)。
点赞 回复 分享
发布于 2023-08-04 15:48 江西
我转成数组用归并排序实现了,万万没想到还能用指针来做,学习了
点赞 回复 分享
发布于 2023-09-01 16:18 广东
我C++归并排序也内存超限了,感觉还是得原地归并
点赞 回复 分享
发布于 05-12 16:42 河南

相关推荐

评论
64
16
分享
牛客网
牛客企业服务