LeetCode 148.排序链表的五种解法

题目概述:

在这里插入图片描述
链接:点我做题

思路

一、数组储存法

  这是最好想的一种方法,由于链表不支持随机访问,那么很多像堆排序、计数排序、快速牌数的Hoare方法这样依赖随机访问的算法就不能使用了,但是我们先把数据拷贝到vector容器中,然后调用STL的sort函数对vector容器进行排序,然后再遍历一遍链表把排序后的结果填入链表中。
  这个方法的缺点是空间复杂度是 O ( N ) O(N) O(N)。但是它的效率会很高,因为就我所知STL的sort方法集成了快速排序、小区间优化的插入排序、确认key值时的三数取中、堆排序等几种优化方法,肯定是比我们手写的速度要快很多的。
代码:

class Solution {
public:
    ListNode* sortList(ListNode* head) 
    {
        if (head == nullptr || head->next == nullptr)
        {
            return head;
        }
        vector<int> vec;
        ListNode* cur = head;
        while (cur)
        {
            vec.push_back(cur->val);
            cur = cur->next;
        }
        sort(vec.begin(), vec.end());
        cur = head;
        int i = 0;
        while (cur)
        {
            cur->val = vec[i++];
            cur = cur->next; 
        }
        return head;
    }
};

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
空间复杂度: O ( n ) O(n) O(n)

二、递归归并法

  本方法的思想就是合并有序数组,假设链表的起点和终点的指针分别为 l e f t left left r i g h t right right,那么可以用快慢指针法到达链表的中间结点,设指向链表中间结点的指针是 m i d mid mid,由此可以把链表分割为 [ l e f t , m i d ] [left,mid] [left,mid] [ m i d , r i g h t ] [mid,right] [mid,right],为了后面的归并,在分割过程中要把区间尾指针的 n e x t next next设为null,区间中只有一个节点时,显然这个区间是有序的,然后两个单个结点的区间可以利用合并有序数组的方法进行合并,形成一个由两个结点的有序链表,然后又可以利用合并有序链表的方法,以此类推。
  这个方法的缺点显然是递归会有函数栈帧的消耗,空间复杂度是 O ( l o g n ) O(logn) O(logn)
代码:

class Solution {
public:
    ListNode* sortList(ListNode* head) 
    {
        //控制边界
        if (head == nullptr || head->next == nullptr)
        {
            return head;
        }
        //ListPartion函数会把分割做了,排序会在ListPartion函数里另外调用别的函数
        return ListPartion(head, nullptr);
    }
    ListNode* ListPartion(ListNode* left, ListNode* right)
    {
        if (left == right)
        {
            //如果是只有一个结点的 left等于right了
            //显然单个区间就是有序的 直接返回
            //就直接返回
            if (left != nullptr)
            {
                left->next = nullptr;
            }
            return left;
        }
        if (left->next == right)
        {
            //如果是区间中有两个元素 就把这个区间断开
            left->next = nullptr;
            return left;
        }
        //其他情况 我们用快慢指针法找到中点再断开
        ListNode* fast = left;
        ListNode* slow = left;
        while (fast != right)
        {
            fast = fast->next;
            slow = slow->next;
            if (fast != right)
            {
                fast = fast->next;
            }
        }
        ListNode* mid = slow;
        //直接把归并过程和递归的分割过程写一起
        return MergeSort(ListPartion(left, mid), ListPartion(mid, right));
    }
    ListNode* MergeSort(ListNode* l1, ListNode* l2)
    {
        ListNode* dummyhead = new ListNode;
        ListNode* tail = dummyhead;
        //合并有序链表看谁小把谁放进去就行了
        while (l1 != nullptr && l2 != nullptr)
        {
            if (l1->val < l2->val)
            {
                ListNode* next = l1->next;
                l1->next = tail->next;
                tail->next = l1;
                tail = l1;
                l1 = next;
            }
            else
            {
                ListNode* next = l2->next;
                l2->next = tail->next;
                tail->next = l2;
                tail = l2;
                l2 = next;
            }
        }
        //出来以后 l1和l2可能有一个没有到头,不过剩余部分都已有序 直接接上就行
        tail->next = (l1 == nullptr) ? l2 : l1;
        ListNode* ret = dummyhead->next;
        delete dummyhead;
        return ret;
    }
};

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
空间复杂度: O ( l o g n ) O(logn) O(logn)

三、非递归归并法

  这个方法的思想就是我们放弃递归的想法,去这样思考:我们用size表示当前链表中的有序段的长度,也就是说链表从头开始以 s i z e size size为长度得到的子链表是有序的,显然 s i z e = 1 size = 1 size=1时,每个长度为1的子链表当然是有序的,然后我们要把链表以 s i z e = 1 size=1 size=1长度分割,然后两两进行一个合并有序链表,经过这轮循环后, s i z e ∗ = 2 size*= 2 size=2, s i z e size size变成2,当前链表有序子链表的长度是2,然后再以长度为 s i z e = 2 size = 2 size=2进行分割,然后合并有序链表,以此类推,当size>=链表长度时,显然整个链表就有序了。
  显然我们没有任何的递归调用和数组创建,所以这个方法的空间复杂度是 O ( 1 ) O(1) O(1),时间复杂度则和归并一样是 O ( n l o g n ) O(nlogn) O(nlogn)
代码:

class Solution {
public:
    ListNode* sortList(ListNode* head) 
    {
        //边界条件
        if (head == nullptr || head->next == nullptr)
        {
            return head;
        }
        int len = getListlength(head);
        //设置一个哨兵头结点 这样方便插入
        ListNode* dummyhead = new ListNode(0, head);
        for (int size = 1; size < len; size *= 2)
        {
            //tail变量用来维护此轮已经size大小有序的链表
            //它指向链表的尾结点
            //刚进入循环 显然后没有部分有序 所以tail指向哨兵结点
            ListNode* tail = dummyhead;
            //cur指向当前正在处理的size大小的链表段的头结点
            ListNode* cur = dummyhead->next;
            while (cur)
            {
                //left是第一个归并的两个部分链表中第一个链表的头结点的指针
                ListNode* left = cur;
                //cut函数会把以起点开始size长度的链表截断(把尾指针的next改为nullptr)
                //并且返回尾结点原来的下一个结点的指针
                ListNode* right = cut(left, size);
                //用right接到的显然就是第第二个链表的头结点的指针
                cur = cut(right, size);
                //以此来同时截断第二个链表并且更新cur
                tail->next = ListMergeSort(left, right);
                //把两个size长度的部分有序链表合成的2size有序链表接到tail后面
                while (tail->next)
                {
                    tail = tail->next;
                }
                //更新tail
            }
        }
        ListNode* ret = dummyhead->next;
        delete dummyhead;
        return ret;
    }
    int getListlength(ListNode* head)
    {
        int ret = 0;
        while (head)
        {
            head = head->next;
            ret++;
        }
        return ret;
    }
    ListNode* cut(ListNode* head, int n)
    {
        //cut函数会把以起点开始size长度的链表截断
        //(把尾指针的next改为nullptr)
        //并且返回尾结点原来的下一个结点的指针
        ListNode* cur = head;
        while (--n && cur != nullptr)
        //控制走n-1步 并且不要超过终点
        {
            cur = cur->next;
        }
        if (cur == nullptr)
        {
            //如果到达终点了
            //那返回值就应该是nullptr
            return nullptr;
        }
        ListNode* ret = cur->next;//保存原来的next值用于返回
        cur->next = nullptr;//切割
        return ret;
    }
    ListNode* ListMergeSort(ListNode* l1, ListNode* l2)
    {
        ListNode* dummyhead = new ListNode;
        ListNode* tail = dummyhead;
        while (l1 && l2)
        {
            if (l1->val < l2->val)
            {
                ListNode* next = l1->next;
                l1->next = tail->next;
                tail->next = l1;
                tail = l1;
                l1 = next;
            }
            else
            {
                ListNode* next = l2->next;
                l2->next = tail->next;
                tail->next = l2;
                tail = l2;
                l2 = next;
            }
        }
        tail->next = l1 ? l1 : l2;
        ListNode* ret = dummyhead->next;
        delete dummyhead;
        return ret;
    }
};

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
空间复杂度: O ( 1 ) O(1) O(1)

四、快速排序递归版本

  理论上来讲所有不依赖随机访问而是依赖于++的排序方法都可以在链表排序中使用,显然快排中的前后指针法非常适合于链表。
  关于前后指针法和快速排序的思想与实现,我写过一篇博客,大家如果遗忘或者不熟悉的话可以进入我的博客学习:https://blog.csdn.net/CS_COPy/article/details/121421113?spm=1001.2014.3001.5501
代码:

class Solution {
public:
    ListNode* sortList(ListNode* head) 
    {
        if (head == nullptr || head->next == nullptr)
        {
            return head;
        }
        QuickSortList(head, nullptr);
        return head;
    }
    void QuickSortList(ListNode* left, ListNode* right)
    {
        if (left == right)
        {
            return;
        }
        ListNode* keyi = PartSort(left, right);
        QuickSortList(left, keyi);
        QuickSortList(keyi->next, right);
    }
    ListNode* PartSort(ListNode* left, ListNode* right)
    {
        if (left == right)
        {
            return left;
        }
        int key = left->val;
        ListNode* cur = left;
        ListNode* prev = left;
        while (cur != right)
        {
            if (cur->val < key)
            {
                prev = prev->next;
                if (prev != cur)
                {
                    int tmp = cur->val;
                    cur->val = prev->val;
                    prev->val = tmp;
                }
            }
            cur = cur->next;
        }
        left->val = prev->val;
        prev->val = key;
        return prev;
    }
};

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
空间复杂度: O ( l o g n ) O(logn) O(logn)

五、快速排序前后指针法非递归

  我们用栈来模拟这个递归过程,这个方法在我的那篇博客里也有讲,其实就是把区间的左端点 l e f t left left和右端点 r i g h t right right作为栈的元素入栈,然后从栈中取出一组左右端点,然后进行一个前后指针法的单趟排序,得到一个分割点keyi(keyi这个位置的元素就已经排好了),然后把 [ l e f t , k e y i ] [left,keyi] [left,keyi](如果 l e f t ! = k e y i left != keyi left!=keyi)和 [ k e y i − > n e x t , r i g h t ] [keyi->next, right] [keyi>next,right]入栈,循环往复,直到栈空为止。

class Solution {
public:
    ListNode* sortList(ListNode* head) 
    {
        if (head == nullptr || head->next == nullptr)
        {
            return head;
        }
        QuickSortListNOR(head, nullptr);
        return head;
    }
    void QuickSortListNOR(ListNode* head, ListNode* tail)
    {
        stack<ListNode*> st;
        st.push(head);
        st.push(tail);
        while (!st.empty())
        {
            ListNode* right = st.top();
            st.pop();
            ListNode* left = st.top();
            st.pop();
            ListNode* keyi = PartSort(left, right);
            if (keyi != left)
            {
                st.push(left);
                st.push(keyi);
            }
            if (keyi->next != right)
            {
                st.push(keyi->next);
                st.push(right);
            }
        }
    }
    ListNode* PartSort(ListNode* left, ListNode* right)
    {
        int key = left->val;
        ListNode* cur = left;
        ListNode* prev = left;
        while (cur != right)
        {
            if (cur->val < key)
            {
                prev = prev->next;
                if (prev != cur)
                {
                    int tmp = cur->val;
                    cur->val = prev->val;
                    prev->val = tmp;
                }
            }
            cur = cur->next;
        }
        left->val = prev->val;
        prev->val = key;
        return prev;
    }
};

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
空间复杂度: O ( 1 ) O(1) O(1)

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值