LeetCode 148.排序链表

题目

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

示例 1:

LeetCode 148.排序链表_第1张图片

输入:head = [4,2,1,3]
输出:[1,2,3,4]

示例 2:

LeetCode 148.排序链表_第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);//归并前后两部分
}

LeetCode 148.排序链表_第3张图片

 

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

复杂度分析

时间复杂度:归并排序,时间复杂度为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&&inext;
    }
    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;stepnext=Mergedata(h1,h2);//合并第一与第二部分,并尾插在s之后
            while(cur->next){//cur往前走,便于下次尾插
                cur=cur->next;
            }
        }
    }
    return s.next;
}

LeetCode 148.排序链表_第4张图片

 复杂度分析

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

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

你可能感兴趣的:(链表,数据结构,排序链表,力扣题目,归并排序)