leetcode:148. 排序链表

题目来源

148. 排序链表

题目描述

在这里插入图片描述

struct ListNode {
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};


class Solution {
public:
    ListNode* sortList(ListNode* head) {

    }
};

题目解析

常见排序方法有很多,插入排序,选择排序,堆排序,快速排序,冒泡排序,归并排序,桶排序等等。。它们的时间复杂度不尽相同,而这里题目限定了时间必须为O(nlgn),符合要求只有快速排序,归并排序,堆排序,而根据单链表的特点,最适合用归并排序。

为什么呢?这是由于链表不能随机访问,所以

  • 快排实现会比较麻烦
  • 堆排序的话,如果让新建结点的话,还是可以考虑的,若只能交换结点,最好还是不要用
  • 归并排序因为其可以利用递归来交换数据,天然适合链表这种结构

归并排序

时间复杂度O(nlogn),不考虑递归栈空间的话空间复杂度是O(1),其数组实现可以参考[算法:归并排序]

核心:leetcode:21. 合并两个有序链表

归并排序的核心是一个merge()函数,其主要功能是实现两个有序链表的合并。

算法: 将链表从中间断开【快慢指针取中】,分成连部分,左右两边在分别分开。。。一直到只剩下一个节点【这个时候就没有方法在拆分了】,然后就可以merge了

cpp

 class Solution {
    ListNode *merge(ListNode *l1, ListNode *l2){
        ListNode *dummy = new ListNode(-1);
        ListNode *move = dummy;
        while (l1 && l2){
            if(l1->val < l2->val){
                move->next = l1;
                l1 = l1->next;
            }else{
                move->next = l2;
                l2 = l2->next;
            }
            move = move->next;
        }
        if(l1){
            move->next = l1;
        }else{
            move->next = l2;
        }
        return dummy->next;
    }
public:
    ListNode* sortList(ListNode* head){
        if(!head || !head->next){
            return head;
        }
        ListNode *slow = head, *fast = head, *pre = nullptr;
        while (fast != NULL && fast->next != NULL){
            pre = slow;
            slow = slow->next;
            fast = fast->next->next;
        }
        pre->next = NULL;
        //因为快指针每次走两步,慢指针每次走一步,当快指针到达链表末尾时,慢指针正好走到中间位置,参见代码如下:
        return merge(sortList(head), sortList(slow));
    }
};

选择排序

数组实现

  • 开始时默认整个链表都是未排序部分
  • 每次在未排序部分找到最小值的节点,并把这个节点从未排序链表中删除,删除的过程中需要保存不断链。比如:2->1->3,变成2->3,并返回要删除链表的前一个节点
  • 把删除的节点连接到排好序部分的链表尾部
class Solution {
    ListNode *getSmallestPreNode(ListNode*head){
        ListNode *smallPre = nullptr, *small = head;
        ListNode *prev = head, *curr = head->next;
        while (curr != nullptr){  // 是否还有比small更小的节点
            if(curr->val < small->val){
                smallPre = prev;
                small = curr;
            }
            prev = curr;
            curr = curr->next;
        }
        return smallPre;
    }
public:
    ListNode* sortList(ListNode* head) {
        if(head == nullptr || head->next == nullptr){
            return nullptr;
        }
        
        ListNode *tail = nullptr;// 排序部分尾部(刚开始是没有有序部分)
        ListNode *curr = head; // 未排序部分头部(开始时默认整个链表都是未排序部分)
        ListNode *smallPre = nullptr; // 最小节点的前一个节点
        ListNode *small = nullptr;  // 最小节点
        while (curr != nullptr){// 还有未排序部分
            small = curr;
            smallPre = getSmallestPreNode(curr); // 去未排序部分找最小的节点
            // 将这个最小值从当前链表中删除
            if(smallPre != nullptr){ // curr不是最小值
                small = smallPre->next;
                smallPre->next = small->next;
            }
            curr = curr == small ? curr->next : small;
            //将small加到tail的尾部
            if(tail == nullptr){
                head = small;
                tail = small;
            }else{
                tail->next = small;
                tail = small;
            }
        }
        return head;
    }
};

冒泡排序

  • 冒泡排序是两两相邻比较的
    时间复杂度O(n^2),空间复杂度O(1)
class Solution {
  
public:
    ListNode* sortList(ListNode* head) {
        if(head == nullptr || head->next == nullptr){
            return nullptr;
        }
        
        ListNode *cur = head;
        ListNode *tail = nullptr;  // 初始时:链表的最后一个节点指向null
        bool  flag;
        while (cur != tail){
            flag = false;
            while (cur->next != tail){ // 无序区还有节点可以比较
                if (cur->val > cur->next->val){ // 当前节点和当前节点的下一个节点比较
                    std::swap(cur->val, cur->next->val);
                    flag = true;
                }
                cur = cur->next;
            }
            if (!flag){ // 经过一轮比较没有泡泡
                break;
            }

            // 跳出循环时:cur.next = null,cur是链表的最后一个节点
            tail = cur;  // 第一次循环后,尾巴节点前面移动
            cur = head;
        }

        return head;
    }
};

梳子排序是冒泡排序的改进,它需要跨索引比较,而链表必须一个个访问,因此pass

地精排序需要来回冒泡,也就是说要回退链表,而链表回退是很难的

奇偶排序

class Solution {
  
public:
    ListNode* sortList(ListNode* head) {
        if(head == nullptr || head->next == nullptr){
            return nullptr;
        }
        
        bool flag = true; //默认当前链表是需要比较的
        while (flag){
            flag = false;  // 默认已经比较完成了

            // 先进行奇数位排序
            for (ListNode *cur = head; cur != nullptr &&  cur->next != nullptr ; cur = cur->next->next){
                if (cur->val > cur->next->val){
                    std::swap(cur->val, cur->next->val);
                    flag = true;
                }
            }

            // 偶数位的排序
            for (ListNode *cur = head->next; cur != nullptr &&  cur->next != nullptr ; cur = cur->next->next){
                if (cur->val > cur->next->val){
                    std::swap(cur->val, cur->next->val);
                    flag = true;
                }
            }
        }
        return head;
    }
};

在这里插入图片描述

计数排序&&桶排序

因为不知道链表的最大值和最小值,也就不知道要分配多大的桶,这是不现实的,因此pass

类似题目

题目核心思路
leetcode:21. 合并两个有序链表 Merge Two Sorted Lists比较当前两个节点值大小,然后移动较小的那个
leetcode:23. 合并K个升序链表 Merge k Sorted Lists逐一合并两条链表;分治合并;k指针;优先队列(将链表的值存入小根堆中,再逐次将堆顶取出连接成链表)
leetcode:148. 排序链表 Sort List归并排序:将链表从中间断开【快慢指针取中】,分成连部分,左右两边在分别分开。。。一直到只剩下一个节点【这个时候就没有方法在拆分了】,然后就可以merge了
leetcode:147. 对链表进行插入排序 Insertion Sort List
leetcode:708. 循环有序列表的插入 Insert into a Cyclic Sorted List
leetcode:75. 颜色排序 Sort Colors
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值