• 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);
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 自底向上 空间复杂度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;                     // 返回合并后的头结点
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    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;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

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

    3. 插入排序

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

  • 相关阅读:
    java迷宫寻找最短路径
    编程参考 - C++里的类指针不要乱传
    数据仓库【指标体系】
    R语言 pca主成分分析的主要方法
    基于Django开发的图书管理推荐、电影推荐、课程推荐系统、大众点评店铺推荐系统、健身课程管理系统
    使用水泥路肩培土两用机进行施工一次制作成型
    使用crictl pull时报错:“unknown service runtime.v1alpha2.ImageService”
    【word技巧】ABCD选项如何对齐?
    chatgpt的插件功能下架了吗?
    Spring的SmartLifecycle可以没用过,但没听过就不好了! - 第517篇
  • 原文地址:https://blog.csdn.net/qq_19887221/article/details/126368896