LeetCode 148.排序链表

本文详细介绍了如何使用归并排序算法对链表进行排序,包括递归和非递归两种实现方式。递归方法通过找到链表中点,将链表拆分为两部分并分别排序,再进行合并。非递归方法则通过不断拆分和合并子链表,逐步完成排序,空间复杂度为O(1)。整个过程涉及到了链表操作、寻找中点、合并有序链表等技巧,最后分析了时间复杂度和空间复杂度。
摘要由CSDN通过智能技术生成

题目

给你链表的头结点 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) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

链表排序,还是有一定难度的,这里有这样一种思路:先把链表遍历一遍,得到元素个数,然后用malloc申请对应个数的空间,将链表元素全部存入申请出来的数组中,在用一些排序方式(快排、希尔、堆排 等)对数组进行排序,然后把排好序的数据在写回到链表中。

在这里我不使用以上的思路,而是要直接对链表进行操作,从而达到链表排好序的目的。其中递归和非递归的代码思路都是归并排序。

递归

思路

1、找到链表的中点,以中点为分界,将链表拆分成两个子链表。

2、对两个子链表分别排序。

3、将两个排序后的子链表合并,得到完整的排序后的链表。

上述过程可以通过递归实现。递归的终止条件是链表的节点个数小于或等于 1,即当链表为空或者链表只包含 1个节点时,不需要对链表进行拆分和排序。

寻找链表的中点可以使用快慢指针的做法,快指针每次移动 2步,慢指针每次移动 1 步,当快指针到达链表末尾时,慢指针指向的链表节点即为链表的中点。

合并两个链表可以用尾插的方法,将节点不断尾插到给定的表头之后。

代码

struct ListNode* Mergedata(struct ListNode* head1,struct ListNode* head2){//合并两个有序链表
    struct ListNode s;//给定链表头
    struct ListNode* cur=&s;//头指针,指向链表头
    while(head1&&head2){//将两个有序链表进行尾插到s之后
        if(head1->val<=head2->val){
            cur->next=head1;
            head1=head1->next;
        }else{
            cur->next=head2;
            head2=head2->next;
        }
        cur=cur->next;
    }
    if(head1){//尾插剩余部分
        cur->next=head1;
    }else{
        cur->next=head2;
    }
    return s.next;
}
struct ListNode* Middle(struct ListNode* head){
    //寻找链表的中间节点,并返回地址,注意此函数将把一个链表一分为二。
    struct ListNode* prev=NULL;
    struct ListNode* slow=head;
    struct ListNode* fast=head;
    while(fast&&fast->next){
        prev=slow;
        slow=slow->next;
        fast=fast->next->next;
    }
    prev->next=NULL;
    return slow;
}
struct ListNode* sortList(struct ListNode* head){
    if(head==NULL||head->next==NULL){
        return head;
    }
    struct ListNode* cur=Middle(head);//拿到中间节点地址
    head=sortList(head);//将前半部分排好序
    cur=sortList(cur);//将后半部分排好序
    return Mergedata(head,cur);//归并前后两部分
}

 

看完以上代码,使用递归的话,就会发现这道题其实就是:合并两个有序链表、找到链表的中间节点、归并排序 这三道题的融合。

复杂度分析

时间复杂度:归并排序,时间复杂度为O(NlogN)

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

非递归

因为题目进阶要求空间复杂度为O(1),很显然以上递归代码不满足要求。那么此时就需要想办法将递归转循环了。

思路

首先求得链表的长度 len,然后将链表拆分成子链表进行合并。

具体做法如下:

1、用step表示每次需要排序的子链表的长度,初始时step=1;

2、每次将链表拆分成若干个长度为step的子链表(最后一个子链表的长度可以小于 step),按照每两个子链表一组进行合并,合并后即可得到若干个长度为2*step的有序子链表(最后一个子链表的长度可以小于2*step)。

3、将 step的值加倍,重复第 2 步,对更长的有序子链表进行合并操作,直到有序子链表的长度大于或等于 len,整个链表排序完毕。

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* Mergedata(struct ListNode* head1,struct ListNode* head2){//合并两个有序链表
    struct ListNode s;
    struct ListNode* cur=&s;
    while(head1&&head2){
        if(head1->val<=head2->val){
            cur->next=head1;
            head1=head1->next;
        }else{
            cur->next=head2;
            head2=head2->next;
        }
        cur=cur->next;
    }
    if(head1){
        cur->next=head1;
    }else{
        cur->next=head2;
    }
    return s.next;
}
struct ListNode* Disconnect(struct ListNode* head,int step){
    //将链表按照step长度的拆分,并返回拆分后剩余部分的地址
    if(head==NULL){
        return NULL;
    }
    struct ListNode* cur=head;
    for(int i=0;head&&i<step;i++){
        cur=head;
        head=head->next;
    }
    cur->next=NULL;
    return head;
}
int Length(struct ListNode* head){//求链表长度
    int count=0;
    while(head){
        head=head->next;
        count++;
    }
    return count;
}
struct ListNode* sortList(struct ListNode* head){
    struct ListNode s;//给定头节点,方便尾插
    struct ListNode* cur=&s;//指向给定的头节点
    cur->next=head;//先连接给定的链表,这一步必须做
    int len=Length(head);
    for(int step=1;step<len;step*=2){//step<len,归并排序继续
        head=s.next;//更新head
        cur=&s;//更新cur
        while(head){//每step个一组,进行归并,直到head为空
            struct ListNode* h1=head;//拿到拆分后第一部分
            struct ListNode* h2=Disconnect(h1,step);//拿到拆分后第二部分
            head=Disconnect(h2,step);//拿到拆分后剩余部分
            cur->next=Mergedata(h1,h2);//合并第一与第二部分,并尾插在s之后
            while(cur->next){//cur往前走,便于下次尾插
                cur=cur->next;
            }
        }
    }
    return s.next;
}

 复杂度分析

时间复杂度:归并排序,时间复杂度为O(NlogN)

空间复杂度:没有申请额外空间,空间复杂度为O(1)

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值