148.排序链表

1.题目介绍

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

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

2.题解

147.对链表进行插入排序中我们使用插入排序的方式对于链表进行排序
插入排序的时间复杂度是 O(n^2),其中 n 是链表的长度。这道题考虑时间复杂度更低的排序算法。题目的进阶问题要求达到 O(nlog⁡n) 的时间复杂度和 O(1) 的空间复杂度,时间复杂度是 O(nlog⁡n) 的排序算法包括归并排序、堆排序和快速排序(快速排序的最差时间复杂度是 O(n^2),其中最适合链表的排序算法是归并排序。

2.1 自底向上归并排序

思路

不同于使用递归自上而下的归并排序(从顶层开始逐层向下递归直到单个节点的情况),这里采用自底向上的归并排序方式
主要思路是:
1.遍历链表的得到总长度,从长度为1开始对链表进行拆分归并排序,得到长度为2,4,...Length的有序链表,作为外部大循环。
2.注意每次拆分时,都要隔绝链表和后续节点的联系(curr->next = nullptr)。归并后得到的链表都是相对独立的,这时候我们需要一个指针prev把它们再串成一个大链表,便于下次拆分。
3.总体拆分归并思路是得到两个独立的长度为subLength的有序链表,并获得它们的头结点,再通过调用归并排序函数将二者归并为一个大的有序链表。之后再使用指针prev,设置一个dummyHead虚拟头结点,串起所有子链表,再次进入。
4.由于一次拆分和归并可能并不能覆盖整个链表,即subLength<<1 < Length, 后续还要进行一次次的重复操作,也就是对于链表3,4;链表5,6....,这里还需要在一次的拆分归并操作中,记录下一次操作开始的头结点,这里可以用一个next来记录,并在最后将curr赋值为next,便于下一次循环开始。
5.这里拆分归并可能存在这几种情况:
5.1 最后一次拆分,剩余节点数小于subLength,这样得到head1后,curr遍历到链表1结尾,head2 = curr->next = nullptr; 然后curr = head2 = nullptr;
这里next部分是不走的,因为curr = nullptr; 之后合并head1和head2由于head2==nullptr,直接返回head1;最后prev将前面得到的链表和head1串起来,curr =
next = nullptr;结束本次循环。
5.2 最后一次拆分,剩余节点数小于等于subLength2,这样的到head1,head2后,curr遍历到链表2结尾,这里虽然curr不等于nullptr,走next判断的那一部分,但还是
next = curr->next = nullptr;会在本次结束循环;归并排序也是正常的归并两个链表,然后使用prev串起所有链表,结束。
5.3 最后依次拆分,剩余节点数大于subLength
2,这样的到head1,head2后,curr遍历到链表2结尾,next指向下一次while循环的链表1头结点,同时断掉链表2和后续节
点联系,后续步骤同理。
6.这里为何不直接使用prev = curr;让prev定位到链表末尾呢?这里注意!!!因为经过了归并排序后,链表顺序打乱,原来的curr现在不一定是链表的末尾!!!
while (prev->next != nullptr) { prev = prev->next; }

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        if (head == nullptr)
            return head;
        int length = 0;
        ListNode* node = head;
        while (node != nullptr) {
            length++;
            node = node->next;
        }
        ListNode* dummyHead = new ListNode(0, head);
        /* 每次subLength*2,
         * 将链表从长度为1的小链表归并为长度为2,4,...,length的链表*/
        for (int subLength = 1; subLength < length;
             subLength <<= 1) { //这里<<=相当于+=
            /* prev用于新链表的建立, curr用于拆分链表*/
            ListNode *prev = dummyHead, *curr = dummyHead->next;
            /* 这里由于划分长度不同,一般一次拆分为两份链表归并并不够,需要进行重复多次操作*/
            while (curr != nullptr) {
                /* 1.找到两个独立有序的链表1和链表2 */
                ListNode* head1 = curr;
                for (int i = 1; i < subLength && curr->next != nullptr; i++) {
                    curr = curr->next; //得到链表1尾部节点
                }
                ListNode* head2 = curr->next; //得到链表2头结点
                curr->next = nullptr; //断开链表1和后续节点连接
                curr = head2;         //更新curr位置为链表2头结点
                for (
                    int i = 1;
                    i < subLength && curr != nullptr && curr->next != nullptr;
                    i++) { //这里由于curr变化,while中的curr!=nullptr不能保证这里的curr,所以设置条件
                    curr = curr->next; //得到链表2尾部节点
                }
                ListNode* next = nullptr;
                /* 判断
                1.剩余节点数小于等于subLength,不够分出或刚好分出一个链表2,curr==nullptr,完成本次归并后结束
                2.
                剩余节点数大于sublength,可以分出一个完整链表2,接下来还有剩余,重复while循环*/
                if (curr != nullptr) {
                    next = curr->next;    //只要当前curr不为nullptr
                    curr->next = nullptr; //断掉链表2和后续节点的联系
                }
                /* 2.现在有了两个独立的链表1和链表2,我们就要进行排序归并 */
                ListNode* mergeNode = mergeList(head1, head2);
                prev->next =
                    mergeNode; //由于可能长度固定时,不止一次归并操作,需要用prev链接
                // prev = curr; //这是一种错误的想法,因为上面
                while (prev->next != nullptr) {
                    prev = prev->next;
                }
                curr = next; //为下次链表3和链表4的归并做准备(如果存在)
            }
        }
        return dummyHead->next;
    }

    ListNode* mergeList(ListNode* L1, ListNode* L2) {
        ListNode* prehead = new ListNode(-1);
        ListNode* prev = prehead;
        while (L1 != nullptr && L2 != nullptr) {
            if (L1->val <= L2->val) {
                prev->next = L1;
                prev = L1;
                L1 = L1->next;
            } else {
                prev->next = L2;
                prev = L2;
                L2 = L2->next;
            }
        }
        prev->next = L1 == nullptr ? L2 : L1;
        ListNode* ans = prehead->next;
        delete prehead;
        return ans;
    }
};

2.2 自顶向下归并排序

思路

1.利用快慢指针找到链表中点
2.拆分为两个子链表,并进行递归调用归并排序,自上而下不断拆,直到为单个节点
3.合并归并排序的有序链表。
注意:
1.我们要拆分链表,断绝其与后续节点的联系,这一步可以在我们递归到单个节点时进行(即在返回条件内部写入),直接让单个节点->next = nullptr即可,之后通过归并合并得到的链表也必然是和其他链表相对独立的。
2.注意这里快慢指针的结束条件是!=tail 而不是 !=nullptr
3.这里的tail不是链表尾结点,是链表尾结点的下一个节点。

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        return sortList(head, nullptr);
    }
    
    ListNode* sortList(ListNode* head, ListNode* tail) {
        // 如果当前链表为空,直接返回
        if (head == nullptr) return head;
        
        // 如果当前范围只有一个节点,已经是有序的,直接返回
        if (head->next == tail) {
            head->next = nullptr;
            return head;
        }
        // 使用快慢指针找到中间节点
        ListNode* slow = head, *fast = head;
        while (fast != tail) {
            slow = slow->next;
            fast = fast->next;
            if (fast != tail) fast = fast->next;
        }
        
        // 中间节点作为切分点,递归地对左右两部分进行排序,并合并
        ListNode* mid = slow;
        return mergeList(sortList(head, mid), sortList(mid, tail));
    }
    
    ListNode* mergeList(ListNode* L1, ListNode* L2) {
        ListNode* prehead = new ListNode(-1);
        ListNode* prev = prehead;
        while(L1 != nullptr && L2 != nullptr){
            if(L1->val <= L2->val){
                prev->next = L1;
                prev = L1;
                L1 = L1->next;
            }
            else{
                prev->next = L2;
                prev = L2;
                L2 = L2->next;
            }
        }
         prev->next = L1 == nullptr? L2:L1;  
         ListNode* ans = prehead->next;
         delete prehead;
         return ans;
    }
};
posted @ 2024-01-20 17:41  DawnTraveler  阅读(4)  评论(0编辑  收藏  举报