题解 | #单链表的排序#堆排序最简单而直接

默认你已经理解题意

思路如下

堆排序-简单直接

  1. 定义类型为ListNode的最小堆
  2. 建堆:链表所有node入堆
  3. 依次弹出堆顶node,即是从小到大的顺序
  4. 时间复杂度O(nlogn),空间复杂度O(n)
  5. 此法和数组的堆排序几乎没有区别,实现起来最简单,不易出错
class Solution {
    public ListNode sortList(ListNode head) {
        PriorityQueue<ListNode> heap = new PriorityQueue<ListNode>((a,b)->a.val-b.val);
        while(head!=null)
        {
            heap.offer(head);
            head=head.next;
        }
        ListNode dummy=new ListNode();
        ListNode cur = dummy;
        while(heap.size()>0)
        {
            cur.next=heap.peek();
            heap.poll();
            cur=cur.next;
        }
        cur.next=null;
        return dummy.next;
    }
}

空间复杂度降低-归并

  1. 数组的归并排序需要引入辅助数组用于归并,因此空间复杂度是O(n)
  2. 有序链表的归并不需要额外空间辅助
  3. 自顶向下,对链表进行排序:
  • 若链表元素为1或0:
    • 已经有序,返回头结点
  • 否则
    • 快慢指针法将链表分为两部分,其长度差不超过1
    • 对这两部分链表排序(递归)
    • 合并排序后的两个链表
  1. 时间复杂度是O(nlogn),空间复杂度是O(logn),即递归调用的深度
public class Solution {
    public ListNode SortList(ListNode head) {
        if(head==null || head.next==null) return head;
        var secondHead = Split(head);
        head=SortList(head);
        secondHead=SortList(secondHead);
        return Merge(head,secondHead);
    }
    ListNode Merge(ListNode head1, ListNode head2)
    {
        var dummy=new ListNode(0);
        var cur=dummy;
        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;
        }
        if(head1!=null) cur.next=head1;
        else cur.next=head2;
        return dummy.next;
    }
    ListNode Split(ListNode head)
    {
        ListNode slow=head, fast=head.next;
        while(fast!=null&&fast.next!=null)
        {
            slow=slow.next;
            fast=fast.next.next;
        }
        var newHead = slow.next;
        slow.next=null;
        return newHead;
    }
}

空间复杂度再降低-自底向上的归并

  1. 熟悉归并排序的兄弟应该知道,若采用自下而上的归并,可以把递归调用改为迭代的,不使用额外空间

  2. 但是对于链表来讲,可以把只有1个节点的链表看成有序的,即归并的底

  3. 两个长度为1的链表合并为长度为2的有序链表,那么两个长度为2的链表合并为长度为4的有序链表,以此类推

  4. 现在我们设归并长度是len,当然一开始len=1,每进行一轮归并,len*=2,当len>=原始链表长度的时候,链表的排序就完成了

我们晓得链表始终不能断开的,所以merge方法中不能用null来判断是否合并完一条链表

我们把merge方法设计成

void Merge(ListNode beforeHead, ListNode mid, ListNode afterTail)

  • beforehead用于确定head1和合并的指针的位置
  • mid是链表2的开始head2(头结点),同时也是链表1的结尾哨兵
  • afterTail是链表2的结尾哨兵

那如何确定每次合并的3个参数?

  • 对每个len的首次合并,beforeHead为链表头部之前的哨兵节点
  • mid为beforeHead后面的第len+1个节点
  • afterTail为mid后面的第len个节点
  • 使用上述参数完成一个区间的合并
  • 下一个区间的beforeHeadbeforeHead后面的第len*2个节点
  • 以上移动均需注意是否达到链表尾部的null
  • 若beforeHead为null,本轮合并完成

时间复杂度是O(nlogn),空间复杂度是O(1),只使用了几个变量和1层函数调用

//c#的代码  
public class Solution {
    public ListNode SortList(ListNode head) {
        if(head==null||head.next==null) return head;
        int n=0;
        ListNode dummy = new ListNode();
        dummy.next=head;
        var cur = head;
        while(cur!=null)
        {
            cur=cur.next;
            n++;
        }
        int len=1;
        while(len<n)
        {
            var beforeHead=dummy;
            while(beforeHead!=null)
            {
                var mid = beforeHead.next;
                for(int i=0;i<len;i++)
                {
                    if(mid!=null) mid=mid.next;
                    else break;
                }
                var afterTail=mid;
                for(int i=0;i<len;i++)
                {
                    if(afterTail!=null) afterTail=afterTail.next;
                    else break;
                }
                Merge(beforeHead,mid,afterTail);
                for(int i=0;i<len*2;i++)
                {
                    if(beforeHead!=null)
                        beforeHead=beforeHead.next;
                    else break;
                }
            }
            len*=2;
        }
        return dummy.next;
    }
    void Merge(ListNode beforeHead, ListNode mid, ListNode afterTail)
    {
        var cur = beforeHead;
        var head1 = cur.next;
        var head2 = mid;
        while(head1!=mid&&head2!=afterTail)
        {
            if(head1.val<=head2.val)
            {
                cur.next=head1;
                head1=head1.next;
            }
            else
            {
                cur.next=head2;
                head2=head2.next;
            }
            cur=cur.next;
        }
        while(head1!=mid)
        {
            cur.next=head1;
            head1=head1.next;
            cur=cur.next;
        }
        while(head2!=afterTail)
        {
            cur.next=head2;
            head2=head2.next;
            cur=cur.next;
        }
        cur.next=afterTail;
    }
}
全部评论

相关推荐

耀孝女:就是你排序挂了
点赞 评论 收藏
分享
评论
1
收藏
分享
正在热议
# 25届秋招总结 #
439565次浏览 4481人参与
# 春招别灰心,我们一人来一句鼓励 #
41297次浏览 523人参与
# 阿里云管培生offer #
119505次浏览 2219人参与
# 地方国企笔面经互助 #
7904次浏览 18人参与
# 虾皮求职进展汇总 #
113037次浏览 878人参与
# 实习,投递多份简历没人回复怎么办 #
2453508次浏览 34845人参与
# 北方华创开奖 #
107179次浏览 598人参与
# 实习必须要去大厂吗? #
55545次浏览 959人参与
# 同bg的你秋招战况如何? #
74881次浏览 544人参与
# 提前批简历挂麻了怎么办 #
149742次浏览 1975人参与
# 投递实习岗位前的准备 #
1195546次浏览 18545人参与
# 你投递的公司有几家约面了? #
33155次浏览 188人参与
# 双非本科求职如何逆袭 #
661700次浏览 7392人参与
# 机械人春招想让哪家公司来捞你? #
157576次浏览 2267人参与
# 如果公司给你放一天假,你会怎么度过? #
4705次浏览 53人参与
# 如果你有一天可以担任公司的CEO,你会做哪三件事? #
11170次浏览 252人参与
# 发工资后,你做的第一件事是什么 #
12320次浏览 60人参与
# 工作中,努力重要还是选择重要? #
35479次浏览 384人参与
# 参加完秋招的机械人,还参加春招吗? #
20062次浏览 240人参与
# 实习想申请秋招offer,能不能argue薪资 #
39188次浏览 314人参与
# 我的上岸简历长这样 #
451841次浏览 8086人参与
# 非技术岗是怎么找实习的 #
155825次浏览 2120人参与
牛客网
牛客企业服务