当前位置: 首页 > news >正文

leetcode排序链表

自用

1.归并排序

归并排序(Merge Sort)是一种经典的分治算法,用于将一组数据按照指定顺序(通常是升序或降序)进行排序。它的核心思想是将一个大问题分解成若干个小问题,分别解决这些小问题,然后将结果合并起来,从而得到最终的解决方案。以下是归并排序的详细步骤:

分解(Divide)

  • 步骤描述:将待排序的数组从中间位置分成两个大致相等的子数组。如果数组长度为偶数,则两个子数组长度相等;如果数组长度为奇数,则一个子数组比另一个子数组多一个元素。

  • 举例:对于数组 [8, 4, 23, 42, 16, 15],分解后得到两个子数组 [8, 4, 23][42, 16, 15]

解决(Conquer)

  • 步骤描述:递归地对这两个子数组进行归并排序。递归的基本情况是当子数组的长度为1或0时,认为它已经有序,不需要进一步分解。

  • 举例:对子数组 [8, 4, 23] 进行归并排序,首先将其分解为 [8][4, 23][8] 已经有序,对 [4, 23] 进行归并排序,分解为 [4][23],这两个子数组都有序,然后合并它们得到 [4, 23]。最后将 [8][4, 23] 合并得到 [4, 8, 23]

合并(Combine)

  • 步骤描述:将两个已经排序的子数组合并成一个有序的数组。合并过程中,比较两个子数组的元素,将较小的元素依次放入新的数组中,直到所有元素都被合并。

  • 举例:合并 [4, 8, 23][15, 16, 42],首先比较 415,将 4 放入新数组;然后比较 815,将 8 放入;接着比较 2315,将 15 放入;依此类推,最终得到 [4, 8, 15, 16, 23, 42]

性能分析

  • 时间复杂度:归并排序的时间复杂度为 O(n log n),其中 n 是数组的长度。

  • 这是因为每次分解都将问题规模减半->logn

  • 而合并操作需要线性时间->n

  • 空间复杂度:归并排序需要额外的存储空间来合并子数组,因此其空间复杂度为 O(n)

稳定性

  • 稳定性:归并排序是一种 稳定排序算法。在合并过程中,如果两个元素相等,会先放入来自左侧子数组的元素,从而保持它们的相对顺序。

2.链表+归并

1.排序合并

归并的核心就是拆解排序再合并

假如有两个有序链表该如何合并,这个比较简单,使用双指针即可

参考题目:

https://leetcode.cn/problems/merge-two-sorted-lists

用两个指针分别指向两个链表的开头,比较两个指针指向的数的大小,选择小的,再把这个指针向前移动一步就行

class Solution {
public:ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {ListNode* preHead = new ListNode(-1);ListNode* prev = preHead;while (l1 != nullptr && l2 != nullptr) {if (l1->val < l2->val) {prev->next = l1;l1 = l1->next;} else {prev->next = l2;l2 = l2->next;}prev = prev->next;}// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可prev->next = l1 == nullptr ? l2 : l1;return preHead->next;}
};

2.拆分

每次都从中间拆分,如何找到中间的位置,这里使用快慢指针处理

快指针的速度是慢指针速度的两倍,使得快指针到达末尾时慢指针到一半的位置

ListNode* sortList(ListNode* head, ListNode* tail) {if (head == nullptr) {return head;}if (head->next == tail) {head->next = nullptr;return head;}//特殊判断ListNode* slow = head, *fast = head;while (fast != tail) {slow = slow->next;fast = fast->next;if (fast != tail) {fast = fast->next;}}//快慢指针找到头尾ListNode* mid = slow;return merge(sortList(head, mid), sortList(mid, tail));
//递归调用}

相当于就是对于每一个链表,只要链表元素在2个及以上就可以拆成两个部分放入排序

力扣参考答案:

class Solution {
public:ListNode* sortList(ListNode* head) {return sortList(head, nullptr);}ListNode* sortList(ListNode* head, ListNode* tail) {if (head == nullptr) {return head;}if (head->next == tail) {head->next = nullptr;return head;}ListNode* slow = head, *fast = head;while (fast != tail) {slow = slow->next;fast = fast->next;if (fast != tail) {fast = fast->next;}}ListNode* mid = slow;return merge(sortList(head, mid), sortList(mid, tail));}ListNode* merge(ListNode* head1, ListNode* head2) {ListNode* dummyHead = new ListNode(0);ListNode* temp = dummyHead, *temp1 = head1, *temp2 = head2;while (temp1 != nullptr && temp2 != nullptr) {if (temp1->val <= temp2->val) {temp->next = temp1;temp1 = temp1->next;} else {temp->next = temp2;temp2 = temp2->next;}temp = temp->next;}if (temp1 != nullptr) {temp->next = temp1;} else if (temp2 != nullptr) {temp->next = temp2;}return dummyHead->next;}
};作者:力扣官方题解
链接:https://leetcode.cn/problems/sort-list/solutions/492301/pai-xu-lian-biao-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

3.非递归解法

非递归解法就是基于最开始的分治思想,

从长度1开始对两段链表排序,

然后是长度2的两段链表排序,

直到链表长度超过了总长度的一半,

现在只需要对超过总长度一半的链表和剩下的一段链表排序即可

边界处理比较复杂但是当每一个小段长度是1的时候最容易理解代码

class Solution {
public:ListNode* sortList(ListNode* head) {if (head == nullptr) {return head;}//特判int length = 0;ListNode* node = head;while (node != nullptr) {length++;node = node->next;}//计算长度ListNode* dummyHead = new ListNode(0, head);//设置哨兵for (int subLength = 1; subLength < length; subLength <<= 1) {ListNode* prev = dummyHead, *curr = dummyHead->next;while (curr != nullptr) {ListNode* head1 = curr;for (int i = 1; i < subLength && curr->next != nullptr; i++) {curr = curr->next;}ListNode* head2 = curr->next;curr->next = nullptr;curr = head2;for (int i = 1; i < subLength && curr != nullptr && curr->next != nullptr; i++) {curr = curr->next;}ListNode* next = nullptr;if (curr != nullptr) {next = curr->next;curr->next = nullptr;}ListNode* merged = merge(head1, head2);prev->next = merged;while (prev->next != nullptr) {prev = prev->next;}curr = next;}}return dummyHead->next;}ListNode* merge(ListNode* head1, ListNode* head2) {ListNode* dummyHead = new ListNode(0);ListNode* temp = dummyHead, *temp1 = head1, *temp2 = head2;while (temp1 != nullptr && temp2 != nullptr) {if (temp1->val <= temp2->val) {temp->next = temp1;temp1 = temp1->next;} else {temp->next = temp2;temp2 = temp2->next;}temp = temp->next;}if (temp1 != nullptr) {temp->next = temp1;} else if (temp2 != nullptr) {temp->next = temp2;}return dummyHead->next;}
};作者:力扣官方题解
链接:https://leetcode.cn/problems/sort-list/solutions/492301/pai-xu-lian-biao-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

4.拓展

最直观的方法就是先把所有链表合在一起然后用归并排序处理,但是由于一开始链表就是有序的所以可以利用一下最初有序的条件

这里的标准方法叫做优先队列合并

优先队列

优先队列(Priority Queue) 是一种特殊的队列数据结构,其特点是:

  • 元素按优先级排序:出队顺序由元素的优先级决定,而非入队顺序。

  • 优先级高的元素先出队:每次取出的元素都是当前队列中优先级最高的。

class Solution {
public:struct Status {int val;ListNode *ptr;bool operator < (const Status &rhs) const {return val > rhs.val;}};priority_queue <Status> q;ListNode* mergeKLists(vector<ListNode*>& lists) {for (auto node: lists) {if (node) q.push({node->val, node});}ListNode head, *tail = &head;while (!q.empty()) {auto f = q.top(); q.pop();tail->next = f.ptr; tail = tail->next;if (f.ptr->next) q.push({f.ptr->next->val, f.ptr->next});}return head.next;}
};作者:力扣官方题解
链接:https://leetcode.cn/problems/merge-k-sorted-lists/solutions/219756/he-bing-kge-pai-xu-lian-biao-by-leetcode-solutio-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

结构体 Status

    struct Status {int val;          // 当前节点的值ListNode *ptr;    // 当前节点的指针bool operator<(const Status &rhs) const {return val > rhs.val;   // 注意:这是“小根堆”}
    };
    • 把链表节点包装成一个可比较的小对象。

    • 重载 <反过来写val > rhs.val),使得 priority_queue 内部 值小的在堆顶,形成 小根堆(模拟最小优先队列)。

    • bool
      返回值类型:这个函数最后只回答“是/否”(true / false)。

    • operator<
      函数名字:它重载了“小于号 <”。
      以后写 a < b 时,编译器实际会调用这个函数。

    • (const Status &rhs) const
      参数列表:

      • rhs 是“右操作数”,即 a < b 里的 b

      • 前面加 const 并传引用,保证只读且不开销拷贝。

      • 末尾的 const 告诉编译器“函数体内**绝不修改当前对象(左操作数 a)的任何成员”。

    • return val > rhs.val;
      函数体:

      • 当前对象val 大于 rhsval 时返回 true

      • 效果就是把“更小”的 rhs 判成“排在前面”,从而把小元素推向堆顶。

    • 整体作用
      让标准库的大根堆 priority_queue 误以为“小值更大”,于是堆顶变成最小值——小根堆就这么骗出来了。


    优先队列定义

    priority_queue<Status> q;
    • 默认是 vector 做容器 + 堆算法。

    • 由于上面 < 是反着写的,堆顶永远是 当前最小值


    初始化:把每条链表的头结点全部扔进堆

    for (auto node: lists) {if (node) q.push({node->val, node});
    }
    • 过滤空链表。

    • 每个 Status 记录头节点的值和指针。


    虚拟头节点技巧

    ListNode head, *tail = &head;
    • head 是栈上虚拟头,不存数据。

    • tail 始终指向结果链表的 末尾,方便尾插。


    主循环:每次取最小值接在结果后面

    while (!q.empty()) {auto f = q.top(); q.pop();   // 取当前最小节点tail->next = f.ptr;          // 尾插tail = tail->next;           // 移动尾指针if (f.ptr->next)             // 如果该节点后面还有节点q.push({f.ptr->next->val, f.ptr->next});
    }
    • 弹出堆顶(最小值)→ 接到结果链表。

    • 把“最小值节点的下一个节点”再塞进堆,保证堆中永远 各链表当前未合并的最小候选


    返回结果

    return head.next;
    • 虚拟头的 next 就是合并后的真正头指针。


    复杂度

    • 时间:一共 N 个节点,每次堆操作 O(log k) → O(N log k)

    • 空间:堆最多同时存 k 个节点 → O(k)

    http://www.dtcms.com/a/457328.html

    相关文章:

  1. 做经营性的网站备案条件汉字叔叔花了多少钱做网站
  2. 效果好企业营销型网站建设手机html5网站源码
  3. 浏览器事件循环与内存管理可视化
  4. 虚拟机双网卡设置
  5. 吴恩达机器学习课程(PyTorch适配)学习笔记:2.2 前向传播与推理
  6. 530.二叉搜索树的最小绝对差(二叉树算法题)
  7. 如何解决 pip install -r requirements.txt extras 语法 ‘package[extra’ 缺少 ‘]’ 解析失败问题
  8. 青岛网站建设方案辽阳网站建设
  9. 磁悬浮轴承中基于位移信号的转子位置与转速估计深度解析摘要
  10. 个人和做网站方签合同模板一流的聊城网站建设
  11. 什么是UIOTOS?
  12. 网站建设书籍论文龙网网络推广软件
  13. 蚌埠网站制作公司排名wordpress lazyload
  14. Windows中通过wsl运行Ubuntu
  15. TensorFlow2 Python深度学习 - TensorFlow2框架入门 - 计算图和 tf.function 简介
  16. 怎样理解网站建设与开发这门课郑州妇科医院正规有哪些
  17. 使用 C 语言连接 MySQL 客户端(重点)
  18. 西安网站建设价格明细网站建设项目合同
  19. 中国建设银行密码重置网站邯郸模板建站教程
  20. HFish架构深度解析:从蜜罐诱捕到威胁狩猎的完整技术链路
  21. 最小栈GO实现
  22. 福田欧辉是国企吗做百度手机网站优化点
  23. npm 扩展vite
  24. 和15岁女儿做很舒服网站最新网页版传奇
  25. 01.MMDetection3D训练
  26. 手机 网站制作什么网站资源多
  27. C++之日期类的实现
  28. 构建AI智能体:五十七、LangGraph + Gradio:构建可视化AI工作流的趣味指南
  29. Create/Assemble/Link x64 Windows
  30. 网站建设与管理案例教程第三版答案中国货源大全网