【面试经典 150 | 分治】排序链表

news2024/10/6 18:36:18

文章目录

  • 写在前面
  • Tag
  • 题目来源
  • 解题思路
    • 方法一:链表转数组
    • 方法二:自顶向下归并排序
    • 方法三:自底向上的归并排序
  • 写在最后

写在前面

本专栏专注于分析与讲解【面试经典150】算法,两到三天更新一篇文章,欢迎催更……

专栏内容以分析题目为主,并附带一些对于本题涉及到的数据结构等内容进行回顾与总结,文章结构大致如下,部分内容会有增删:

  • Tag:介绍本题牵涉到的知识点、数据结构;
  • 题目来源:贴上题目的链接,方便大家查找题目并完成练习;
  • 题目解读:复述题目(确保自己真的理解题目意思),并强调一些题目重点信息;
  • 解题思路:介绍一些解题思路,每种解题思路包括思路讲解、实现代码以及复杂度分析;
  • 知识回忆:针对今天介绍的题目中的重点内容、数据结构进行回顾总结。

Tag

【链表】【分治】【归并排序】


题目来源

148. 排序链表


解题思路

方法一:链表转数组

一种朴素的解法是将链表中的节点存储到数组中,然后对数组按节点值进行升序排序,排好序后,将节点数组再连接成一条链表。该方法比较简单,直接给出代码。

代码

/**
 * Definition for singly-linked list.
 * 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:
    // 自定义排序的仿函数
	struct myclass {
		bool operator() (ListNode *n1, ListNode *n2) {
			return n1->val < n2->val;
		}
	} myobject;

	ListNode* sortList(ListNode* head) {
		if (head == nullptr || head->next == nullptr) {
			return head;
		}
		vector<ListNode*> tmp;
		ListNode *curr = head;
		while (curr != nullptr) {
			tmp.push_back(curr);
			curr = curr->next;
		}

		sort(tmp.begin(), tmp.end(), myobject);

		int n = tmp.size();
		head = tmp[0];

		curr = head;
		for (int i = 1; i < tmp.size(); ++i) {
			curr->next = tmp[i];
			curr = curr->next;
		}
        // 最后一个结点的next要置空
        curr->next = nullptr;

		return head;
	}
};

复杂度分析

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn) n n n 是链表中节点个数。本题的时间瓶颈在于排序的时间复杂度。

空间复杂度: O ( n ) O(n) O(n),使用一个额外的数组记录链表中的节点的空间复杂度为 O ( n ) O(n) O(n)。最坏情况下,需要排序的序列是逆序的,需要 n 次递归调用。因此需要 O ( n ) O(n) O(n) 的栈空间

方法二:自顶向下归并排序

对链表进行自顶向下的归并排序步骤如下:

  • 找到链表的中点,以中点为分界,将链表拆分成两个子链表。寻找链表的中间节点可以使用快慢指针来实现,快指针每次移动 2 步,慢指针每次移动 1 步,当快指针到达链表末尾时,慢指针指向的链表节点即为链表的中点。
  • 递归对两个子链表进行排序。
  • 将两个升序的子链表进行合并。合并两个有序链表可以参考 【面试经典150 | 链表】合并两个有序链表。

代码

/**
 * Definition for singly-linked list.
 * 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 {
private:
    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* dummy = new ListNode(0);
        ListNode* prev = dummy;
        while (head1 && head2) {
            if (head1->val < head2->val) {
                prev->next = head1;
                head1 = head1->next;
            }
            else {
                prev->next = head2;
                head2 = head2->next;
            }
            prev = prev->next;
        }

        prev->next = head1 ? head1 : head2;
        return dummy->next;
    }
public:
    ListNode* sortList(ListNode* head) {
        return sortList(head, nullptr);
    }
};

复杂度分析

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn) n n n 是链表中节点个数。

空间复杂度: O ( l o g n ) O(logn) O(logn),空间复杂度取决于递归调用的占空间。

方法三:自底向上的归并排序

归并排序除了自顶向下实现,也可自底向上实现。自底向上的归并排序空间复杂度为 O ( 1 ) O(1) O(1)

首先要求出链表的长度 len。利用迭代可以轻松求出。

接着将链表拆分成子链表进行合并,具体步骤如下:

  • 枚举需要排序的子链表长度,自底向上的排序,初始化子链表长度 subLen = 1
  • 每次将链表拆分成若干个长度为 subLen 的子链表(最后一个链表的长度可以小于 subLen),按照每两个子链表一组进行合并,合并后即可得到若干个长度为 subLen × 2 的有序子链表(最后一个子链表的长度可以小于 subLength × 2。合并两个子链表仍然使用「21. 合并两个有序链表」的做法。
  • subLen 的值加倍,重复第 2 步,对更长的有序子链表进行合并操作,直到有序子链表的长度大于或等于 len,整个链表排序完毕。

代码

/**
 * Definition for singly-linked list.
 * 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 {
private:
    ListNode* merge(ListNode* head1, ListNode* head2) {
        ListNode* dummy = new ListNode(0);
        ListNode* prev = dummy;
        while (head1 && head2) {
            if (head1->val < head2->val) {
                prev->next = head1;
                head1 = head1->next;
            }
            else {
                prev->next = head2;
                head2 = head2->next;
            }
            prev = prev->next;
        }

        prev->next = head1 ? head1 : head2;
        return dummy->next;
    }

public:
    ListNode* sortList(ListNode* head) {
        if (head == nullptr) {
            return head;
        }

        int len = 0;
        ListNode* node = head;
        while (node != nullptr) {
            ++len;
            node = node->next;
        }
        ListNode* dummy = new ListNode(0, head);
        for (int subLen = 1; subLen < len; subLen <<= 1) {
            ListNode* prev = dummy, *cur = dummy->next;
            while (cur) {
                ListNode* head1 = cur;      // 第一个长度为 subLen 的子节点开头
                for (int i = 1; i < subLen && cur->next; ++i) {// 第一个 subLen 子节点末尾的节点
                    cur = cur->next;
                }
                ListNode* head2 = cur->next;// 第二个长度为 subLen 的子节点开头
                cur->next = nullptr;
                cur = head2;
                for (int i = 1; i < subLen && cur && cur->next; ++i) {// 第二个 subLen 子节点末尾的节点
                    cur = cur->next;
                }

                ListNode* next = nullptr;   //  维护下一个长度为 subLen 的子节点开头
                if (cur) {
                    next = cur->next;
                    cur->next = nullptr;
                }
                ListNode* merged = merge(head1, head2);
                prev->next = merged; 
                while (prev->next) {
                    prev = prev->next;
                }
                cur = next;
            }
        }
        return dummy->next;
    }
};

复杂度分析

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn) n n n 是链表中节点个数。

空间复杂度: O ( 1 ) O(1) O(1)


写在最后

如果您发现文章有任何错误或者对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。

如果大家有更优的时间、空间复杂度的方法,欢迎评论区交流。

最后,感谢您的阅读,如果有所收获的话可以给我点一个 👍 哦。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1638495.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

注册表获取autoCAD安装位置

注意事项 注意&#xff1a;①64位操作系统注册表会重定向&#xff0c;RegOpenKeyEx第4个参数得加上KEY_WOW64_64KEY&#xff1b;②RegOpenKeyEx遍历子项时注意第2和第4参数&#xff0c;参考图&#xff1a; ③RegQueryValueEx同样得注意第6参数 完整代码 std::unordered_map…

CAS 与 volatile

目录 CAS volatile 为什么无锁效率高 CAS 的特点 CAS AtomicInteger 内部并没有用锁来保护共享变量的线程安全。那么它是如何实现的呢&#xff1f; public void withdraw(Integer amount) {while(true) {// 需要不断尝试&#xff0c;直到成功为止while (true) {// 比如拿到…

C#知识|泛型集合List相关方法

哈喽&#xff0c;你好&#xff0c;我是雷工&#xff01; 以下为泛型集合List相关方法的学习笔记。 01 集合定义 集合定义的时候&#xff0c;无需规定元素的个数。 02 泛型说明 泛型表示一种程序特性&#xff0c;也就是在定义的时候&#xff0c;无需指定特定的类型&#xff…

STM32 看门狗WDG

一、看门狗&#xff08;Watchdog&#xff09; 看门狗可以监控程序的运行状态&#xff0c;当程序因为设计漏洞、硬件故障、电磁干扰等原因&#xff0c;出现卡死或跑飞现象时&#xff0c;看门狗能及时复位程序&#xff0c;避免程序陷入长时间的罢工状态&#xff0c;保证系统的可靠…

2024五一数学建模A题思路代码与论文分析

2024五一数学建模A题完整代码和成品论文获取↓↓↓↓↓ https://www.yuque.com/u42168770/qv6z0d/gyoz9ou5upvkv6nx?singleDoc# 2024五一数学建模A题钢板最优切割路径问题需要建立的模型和算法: 图论 最短路径算法(Dijkstra算法、Floyd算法等) 动态规划 网格化离散建模 …

Go语言在Web开发中有哪些常用框架?

文章目录 1. Gin原因和解决方案示例代码 2. Echo原因和解决方案示例代码 3. Revel原因和解决方案示例代码 4. Buffalo原因和解决方案示例代码 总结 Go语言在Web开发中拥有许多优秀的框架&#xff0c;这些框架帮助开发者快速构建稳定且高效的Web应用。下面是一些常用的Go语言Web…

# 从浅入深 学习 SpringCloud 微服务架构(七)Hystrix(4)

从浅入深 学习 SpringCloud 微服务架构&#xff08;七&#xff09;Hystrix&#xff08;4&#xff09; 一、hystrix&#xff1a;使用 turbine 聚合所有的 hytrix 的监控数据测试。创建父工程 spring_cloud_hystrix_demo&#xff0c;导入相关依赖坐标。并在父工程 spring_cloud_…

uniapp 桌面应用插件 Ba-Launcher

简介&#xff08;下载地址&#xff09; Ba-Launcher 可以让你的应用成为简单的桌面应用&#xff0c;如需扩展功能&#xff0c;请联系我。 截图展示 可关注博客&#xff0c;实时更新最新插件&#xff1a; uniapp 常用原生插件大全 使用方法 使用方法也很简单&#xff0c;在插…

python中的进程线程和协程

目录 进程&#xff08;Process&#xff09;多进程代码实例 线程&#xff08;Thread&#xff09;多线程存在原因及其缺点多线程代码实例 协程&#xff08;Coroutine&#xff09;协程的优点协程代码实例 进程、线程和协程适合的任务性质和环境多进程更适合的场景多线程更适合的场…

定了!嫦娥六号任务计划5月3日发射 | 最新快讯

来源&#xff1a;央视新闻客户端 今天&#xff08;5 月 1 日&#xff09;&#xff0c;国家航天局发布最新消息&#xff0c;经工程任务指挥部综合研判决策&#xff0c;探月工程四期嫦娥六号任务计划 5 月 3 日实施发射。 发射窗口是指适合火箭发射的时间范围。此次任务在综合考虑…

Devops部署maven项目

这里讲下应用k8s集群devops持续集成部署maven项目的流程。 failed to verify certificate: x509: certificate signed by unknown authority 今天在执行kubectl get nodes的时候报的证书验证问题&#xff0c;看了一圈首次搭建k8s的都是高频出现的问题。 couldn’t get curren…

Unity LensFlare 入门

概述 在项目的制作过程中&#xff0c;太阳光的使用一定是不可缺少的部分&#xff0c;但是如果想实现真实太阳光眼睛看到的镜头炫光效果&#xff0c;那这部分的内容一定不要错过喔&#xff0c;接下来让我们来学习这部分的内容吧&#xff01; Hale(光环效果) Color&#xff1a;…

JavaEE_操作系统之进程(计算机体系,,指令,进程的概念、组成、特性、PCB)

一、冯诺依曼体系&#xff08;Von Neumann Architecture&#xff09; 现代的计算机, 大多遵守冯诺依曼体系结构 CPU 中央处理器: 进行算术运算和逻辑判断.存储器: 分为外存和内存, 用于存储数据(使用二进制方式存储)输入设备: 用户给计算机发号施令的设备.输出设备: 计算机个…

黑马头条Day02_app端文章查看,静态化freemarker,分布式文件系统minIO

文章目录 app端文章查看&#xff0c;静态化freemarker,分布式文件系统minIO1)文章列表加载1.1)需求分析1.2)表结构分析1.3)导入文章数据库1.3.1)导入数据库1.3.2)导入对应的实体类 1.4)实现思路1.5)接口定义1.6)功能实现1.6.1)&#xff1a;导入heima-leadnews-article微服务&am…

Springboot+Vue项目-基于Java+MySQL的智慧校园管理系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

1700java进销存管理系统Myeclipse开发sqlserver数据库web结构java编程计算机网页项目

一、源码特点 java web进销存管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为sqlser…

6.k8s中的secrets资源

一、Secret secrets资源&#xff0c;类似于configmap资源&#xff0c;只是secrets资源是用来传递重要的信息的&#xff1b; secret资源就是将value的值使用base64编译后传输&#xff0c;当pod引用secret后&#xff0c;k8s会自动将其base64的编码&#xff0c;反编译回正常的字符…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-6.5--I.MX6U启动方式

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

LeetCode 69—— x 的平方根

阅读目录 1. 题目2. 解题思路一3. 代码实现一4. 解题思路二5. 代码实现二 1. 题目 2. 解题思路一 二分查找法&#xff0c;对于整数 i ∈ [ 0 , x ] i \in [0,x] i∈[0,x]&#xff0c;我们判断 i 2 i^2 i2 和 x x x 的关系&#xff0c;然后找到最后一个平方小于等于 x x x …

DRF权限组件源码分析

DRF权限组件源码分析 权限组件相关配置同认证组件 0 认证组件的两种返回值 有权限&#xff0c;返回True&#xff0c;程序正常进行无权限&#xff0c;返回False&#xff0c;程序抛出异常 1 单视图应用 2 多视图应用 3 单视图多视图结合 在drf中&#xff0c;默认优先去全局中…