148. 排序链表 ●●

148. 排序链表 ●●

描述

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

进阶:你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

示例

在这里插入图片描述
输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]

题解

1. 归并排序

  • 自顶向下 递归

归并排序主要分为三大步:

  1. 找到中间节点;
  2. 分割链表,并进行左右递归;
  3. 合并左右两部分有序链表,返回合并后的头结点。

在这里插入图片描述

  • 时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn),其中 n 是链表的长度。
  • 空间复杂度: O ( log ⁡ n ) O(\log n) O(logn),其中 n 是链表的长度。空间复杂度主要取决于递归调用的栈空间。
class Solution {
    
    
public:
    ListNode* mergeSort(ListNode* head){
    
    
        if(head == nullptr || head->next == nullptr) return head;       // 终止条件,空节点或只有一个节点
        /*================== 快慢指针确定重点位置 ==================*/
        ListNode* slow = head;
        ListNode* fast = head;
        while(fast->next != nullptr && fast->next->next != nullptr){
    
     
            slow = slow->next;
            fast = fast->next->next;
        }
        /*================== 链表分割,递归 ==================*/
        ListNode* rightHead = slow->next;               // 右半部分链表头结点slow->next
        slow->next = nullptr;                           // 分割链表,指向空节点截断链表
        ListNode* left = mergeSort(head);               // 递归
        ListNode* right = mergeSort(rightHead);         
        /*================== 合并两个有序链表 ==================*/
        ListNode* dummyHead = new ListNode(-1);         // 迭代,LC21.合并两个有序链表
        ListNode* prev = dummyHead;
        while(left != nullptr && right != nullptr){
    
    
            if(left->val < right->val){
    
    
                prev->next = left;
                left = left->next;
            }else{
    
    
                prev->next = right;
                right = right->next;
            }
            prev = prev->next;
        }
        prev->next = left == nullptr? right : left;
        return dummyHead->next;                         // 返回合并后的链表头结点
    }

    ListNode* sortList(ListNode* head) {
    
    
        return mergeSort(head);
    }
};
  • 自底向上 空间复杂度O(1)

使用自底向上的方法实现非递归归并排序,则可以达到 O(1) 的空间复杂度。

在这里插入图片描述
归并排序中,每次分割都是通过二分法得到链表的最小节点单元,然后再通过多轮合并得到排序结果;

每一轮合并都有固定的单元长度,即 step = 1, 2, 4, 8…;

因此,我们可以通过迭代的方法模拟这种分割合并操作。

主要思路为:
1 - 统计链表的长度 n;
2 - 子链表长度 step 从 1 开始成倍增加,不大于 n;
3 - 分割长度为 step 的左链表,并返回右链表的头结点,继续分割,得到下一次合并的头结点;
4- 合并左右链表,并连接到上一次合并的链表中,多轮合并后最终完成排序。

  • 时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn),其中 n 是链表的长度。
  • 空间复杂度: O ( 1 ) O(1) O(1)
class Solution {
    
    
public:
    ListNode* merge(ListNode* left, ListNode* right){
    
     
        ListNode* dummyHead = new ListNode(-1);     // LC21.合并两个有序链表
        ListNode* prev = dummyHead;
        while(left != nullptr && right != nullptr){
    
    
            if(left->val < right->val){
    
    
                prev->next = left;
                left = left->next;
            }else{
    
    
                prev->next = right;
                right = right->next;
            }
            prev = prev->next;
        }
        prev->next = left == nullptr? right : left;
        ListNode* head = dummyHead->next;
        delete dummyHead;
        return head;                                // 返回合并后的链表头结点
    }

    ListNode* cut(ListNode* head, int len){
    
             // 从 head 开始分割长度为 len 的链表,并返回下一个链表的头结点
        if(head == nullptr) return nullptr;
        for(int i = 1; i < len; ++i){
    
               
            if(head == nullptr) return nullptr;     // 最后一个单元长度可能小于 len,因此返回 null
            head = head->next;
        }
        if(head != nullptr){
    
    
            ListNode* nextHead = head->next;
            head->next = nullptr;
            return nextHead;                        // 返回下一个链表的头结点
        }else{
    
    
            return nullptr;
        }
    }

    ListNode* sortList(ListNode* head) {
    
    
        if(head == nullptr || head->next == nullptr) return head;
        int n = 0;
        ListNode* curr = head;
        while(curr != nullptr){
    
    
            ++n;
            curr = curr->next;                      // 统计链表长度
        }
        ListNode* dummyHead = new ListNode(0, head);// 虚拟头结点
        for(int step = 1; step < n; step *= 2){
    
         // step = step * 2
            ListNode* prev = dummyHead;             // 上一次合并链表的尾节点 prev
            ListNode* curr = dummyHead->next;       // 遍历节点
            while(curr != nullptr){
    
    
                ListNode* head1 = curr;             // 左链表头结点
                ListNode* head2 = cut(head1, step); // 分割左链表,并返回右链表头结点
                curr = cut(head2, step);            // 分割右链表,并返回下一次合并的头结点
                prev->next = merge(head1, head2);   // 合并,连接链表
                while(prev->next != nullptr){
    
    
                    prev = prev->next;              // 更新合并链表的尾节点
                }
            }
        }
        return dummyHead->next;                     // 返回合并后的头结点
    }
};

2. 快速排序

通过交换节点值来实现快速排序,并未进行节点指针操作

每次选取下标为 n/4 的节点作为分界值,可以避免极端情况下的超时结果。

  • 时间复杂度:选取分界值复杂度 O ( n ) O(n) O(n),分区复杂度 O ( n ) O(n) O(n),递归复杂度是 O ( l o g n ) O(logn) O(logn)
    快速排序的最优时间复杂度和平均时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn) ,最坏时间复杂度为 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度:递归层数 O ( l o g n ) O(logn) O(logn)
class Solution {
    
    
public:
    void Qsort(ListNode* head, int cnt){
    
                        // 对从 head 开始的 cnt 个节点进行快速排序
        if(head == nullptr || cnt == 0) return;             // 排序结束
        ListNode* curr = head;
        for(int i = 0; i < cnt/4; ++i) curr = curr->next;   // (随机)选取分界值
        swap(head->val, curr->val);
        int pivot = head->val;                              // 当前排序的分界值 
        ListNode* curr_changed = head;                      // 交换过的最后一个节点
        curr = head->next;
        int changeCnt = 0;                                  // 节点交换的次数
        for(int i = 1; i < cnt; ++i){
    
                           // 遍历待排序的所有节点
            if(curr->val < pivot){
    
                              
                curr_changed = curr_changed->next;          // 左右划分
                swap(curr_changed->val, curr->val);
                ++changeCnt;
            }
            curr = curr->next;
        }
        swap(head->val, curr_changed->val);                 // 交换分界值到合适位置
        Qsort(head, changeCnt);                             // 递归前半部分
        Qsort(curr_changed->next, cnt-changeCnt-1);         // 递归后半部分
    }

    ListNode* sortList(ListNode* head) {
    
    
        int n = 0;
        ListNode* curr = head;
        while(curr != nullptr){
    
         // 统计链表长度
            ++n;
            curr = curr->next;
        } 
        if(n <= 1) return head;     
        Qsort(head, n);             // 排序
        return head;
    }
};

快速排序 操作链表节点
快速排序 操作链表节点

3. 插入排序

时间复杂度: O ( n 2 ) O(n^2) O(n2),超出时间限制。

猜你喜欢

转载自blog.csdn.net/qq_19887221/article/details/126368896