精选50题之 148. 排序链表

本文介绍了如何在O(n log n)时间复杂度和常数级空间复杂度下对链表进行排序,以LeetCode的148题为例,详细解析了归并排序的解题思路和代码实现。文章分析了快排、归并、希尔等排序算法,并指出归并排序在链表排序中的应用,同时讨论了自顶向下和自下而上的归并排序方法及其空间复杂度。
摘要由CSDN通过智能技术生成

腾讯精选练习(50 题)之 148. 排序链表

原题目链接

在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。

示例 1:

输入: 4->2->1->3
输出: 1->2->3->4

示例 2:

输入: -1->5->3->4->0
输出: -1->0->3->4->5

直接实现

使用STL的快排实现,仅供参考

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        if(!head)
            return head;
        vector<int> tmpArray; // 定义暂存容器
        ListNode *p = head;
        while(p)
        {
            tmpArray.push_back(p -> val);
            p = p -> next;
        }
        sort(tmpArray.begin(), tmpArray.end()); // STL排序
        p = head;
        for(int i = 0; i < tmpArray.size(); i++)
        {
            p -> val = tmpArray[i];
            p = p -> next;
        }
        return head;
    }
};

在这里插入图片描述
复杂度分析:

  • 时间复杂度:O(NlogN) 排序时的复杂度
  • 空间复杂度:O(N)

题目分析

很多排序算法都可以实现O(NlogN)的时间复杂度:基于二分思想的快排、归并、希尔排序、直接排序、堆排序 都满足。

然而,有需要同时满足空间复杂度为O(1)的排序算法是…希尔排序,归并排序,堆排序
相比之下,归并排序是最常见的,所以就以它作为基础。

解题分析

归并排序三部曲:

  • fast-slow找中点:直到快指针移至末尾,慢指针指向的位置就是中间位置
  • 将链表分成两部分
  • 合并两个有序链表

代码实现

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        return mergesort(head);
    }
    
    ListNode* mergesort(ListNode* node)
    {
        if(!node || !node -> next) 
            return node;
        ListNode *fast = node;
        ListNode *slow = node;
        ListNode *jmp = node;
        while(fast && fast -> next)
        {
            fast = fast -> next -> next;
            jmp = slow;
            slow = slow -> next;
        }
        jmp -> next = nullptr;
        ListNode *l1 = mergesort(node);
        ListNode *l2 = mergesort(slow);
        return merge(l1, l2);
    }
    ListNode *merge(ListNode* l1,ListNode* l2)
    {
        if(l1 == NULL)
            return l2;
        if(l2 == NULL)
            return l1;
        if(l1 -> val < l2 -> val)
        {
            l1 -> next = merge(l1 -> next, l2);
            return l1;
        }
        else
        {
            l2 -> next = merge(l2 -> next, l1);
            return l2;
        }
    }
};

在这里插入图片描述
看到运行结果,内存消耗还是很大,看起来还像是O(N)

复杂度分析:

  • 时间复杂度:O(NlogN)
  • 空间复杂度:O(N)

返回检查代码,发现在递归调用的过程中用到的空间复杂度不是O(1),而是O(N)?……
注:这里留个坑,没有分析清除。

更正

归并排序其实有两种方式:

  • 比较常见的上面用到的 自顶向下
  • 自下而上
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        ListNode dummyHead(0);
        dummyHead.next = head;
        auto p = head;
        int length = 0;
        while (p) {
            ++length;
            p = p->next;
        }
        
        for (int size = 1; size < length; size <<= 1) {
            auto cur = dummyHead.next;
            auto tail = &dummyHead;
            
            while (cur) {
                auto left = cur;
                auto right = cut(left, size); // left->@->@ right->@->@->@...
                cur = cut(right, size); // left->@->@ right->@->@  cur->@->...
                
                tail->next = merge(left, right);
                while (tail->next) {
                    tail = tail->next;
                }
            }
        }
        return dummyHead.next;
    }
    
    ListNode* cut(ListNode* head, int n) {
        auto p = head;
        while (--n && p) {
            p = p->next;
        }
        
        if (!p) return nullptr;
        
        auto next = p->next;
        p->next = nullptr;
        return next;
    }
    
    ListNode* merge(ListNode* l1, ListNode* l2) {
        ListNode dummyHead(0);
        auto p = &dummyHead;
        while (l1 && l2) {
            if (l1->val < l2->val) {
                p->next = l1;
                p = l1;
                l1 = l1->next;       
            } else {
                p->next = l2;
                p = l2;
                l2 = l2->next;
            }
        }
        p->next = l1 ? l1 : l2;
        return dummyHead.next;
    }
};

在这里插入图片描述
复杂度分析:

  • 时间复杂度:O(NlogN)
  • 空间复杂度:O(1)

写在最后

留的坑早点填

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值