【LeetCode-链表】排序链表

题目描述

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

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

题目链接: https://leetcode-cn.com/problems/sort-list/

思路1

使用两个栈来做,类似于栈排序的方法。代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        if(head==nullptr) return head;

        stack<ListNode*> s;
        stack<ListNode*> help; // 辅助栈
        ListNode* cur = head;
        while(cur!=nullptr){
            if(s.empty()) s.push(cur);
            else{
                while(!s.empty() && cur->val>s.top()->val){
                    help.push(s.top()); s.pop();
                }
                s.push(cur);
                while(!help.empty()){
                    s.push(help.top()); help.pop();
                }
            }
            cur = cur->next;
        }

        ListNode* dummy = new ListNode(0);
        cur = dummy;
        while(!s.empty()){
            ListNode* node = s.top(); s.pop();
            node->next = nullptr;
            cur->next = node;
            cur = cur->next;
        }
        return dummy->next;
    }
};
// 超时

这种方不满足 O(n log n) 时间复杂度和常数级空间复杂度,并且会超时。

思路2

使用归并排序。

先划分,当前节点为空或者当前节点的下一个节点为空就返回合并。在分割的时候需要找到链表的中点分割。如果链表长度是奇数,则只有唯一的中点;如果链表长度是偶数,则有两个中点,需要使用左边的中点,具体可参考链表的中间节点

代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        if(head==nullptr || head->next==nullptr) return head; // 注意条件的后半部分

        ListNode* slow = head;
        ListNode* fast = head;
        while(fast->next!=nullptr && fast->next->next!=nullptr){   // 注意:链表长度为偶数,使用左边的中点
            fast = fast->next->next;
            slow = slow->next;
        } 

        ListNode* temp = slow->next;
        slow->next = nullptr;
        ListNode* left = sortList(head);
        ListNode* right = sortList(temp);

        ListNode* dummy = new ListNode(0);
        ListNode* cur = dummy;
        while(left!=nullptr && right!=nullptr){
            if(left->val<right->val){
                cur->next = left;
                left = left->next;
            }else{
                cur->next = right;
                right = right->next;
            }
            cur = cur->next;
        }
        cur->next = (left==nullptr? right:left);
        return dummy->next;
    }
};

这样写也行:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        return mergeSort(head, NULL);
    }

    ListNode* mergeSort(ListNode* left, ListNode* right){
        if(left==NULL || left->next==NULL) return left;   // 注意返回条件

        ListNode* slow = left;
        ListNode* fast = left;
        while(fast->next!=right && fast->next->next!=right){
            slow = slow->next;
            fast = fast->next->next;
        }
        ListNode* next = slow->next;
        slow->next = NULL;
        ListNode* list1 = mergeSort(left, slow);
        ListNode* list2 = mergeSort(next, right);
        return mergeTwoLists(list1, list2);
    }

    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2){
        if(list1==NULL) return list2;
        if(list2==NULL) return list1;

        ListNode* dummy = new ListNode(0);
        ListNode* cur = dummy;
        while(list1!=NULL && list2!=NULL){
            if(list1->val<list2->val){
                cur->next = list1;
                list1 = list1->next;
            }else{
                cur->next = list2;
                list2 = list2->next;
            }
            cur = cur->next;
        }
        if(list1!=NULL) cur->next = list1;
        if(list2!=NULL) cur->next = list2;
        return dummy->next;
    }
};

这样写可以和(有序链表转换二叉搜索树)[https://www.cnblogs.com/flix/p/13307886.html]统一起来。

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(logn)

思路3

使用快速排序。

对于数组的快排算法,核心是进行划分 (partition),确定一个轴点,将小于等轴点的数调整在轴点之前,将大于等于轴点的数调到轴点之后,然后返回轴点的下标。在划分算法中,需要分别从数组的前面和后面对数组进行遍历,后面的下标向前移动。但是在单链表中,并没有向前的指针,无法向链表前面移动。 确定第一个元素的轴点 定义两个指针,fast 和 slow。(目标是在从头到 slow 之间的元素均小于等于轴点;slow 到 fast 之间的元素均大于等于轴点); fast 向后移动,如果 fast < pivot,则 slow 先后移,然后交换 slow 和 fast 的值。这样可以保证 slow 永远是最后一个小于等于 pivot 的元素,而 slow 的下一个是大于等于 pivot 的元素。

代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        quickSort(head, NULL);
        return head;
    }

    void quickSort(ListNode* head, ListNode* tail){
        if(head==NULL || head==tail) return;

        ListNode* pivot = partition(head, tail);
        quickSort(head, pivot);
        quickSort(pivot->next, tail);
    }

    ListNode* partition(ListNode* begin, ListNode* end){
        if(begin==end) return begin;

        ListNode* slow = begin;
        ListNode* fast = begin->next;

        while(fast!=end){
            if(fast->val < begin->val){
                slow = slow->next;
                swap(fast->val, slow->val);
            }
            fast = fast->next;
        }
        swap(begin->val, slow->val);
        return slow;
    }
};
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(logn)

参考

posted @ 2020-07-13 20:37  Flix  阅读(240)  评论(0编辑  收藏  举报