《剑指 Offer》专项突破版 - 面试题 77 和 78 : 详解归并排序(C++ 实现)

目录

归并排序详解

递归实现

迭代实现

面试题 77 : 链表排序

面试题 78 : 合并排序链表

法一、利用最小堆选取值最小的节点

法二、按照归并排序的思路合并链表


 


归并排序详解

归并排序就是将两个或两个以上的有序表合并成一个有序表的过程。将两个有序表合并成一个有序表的过程称为 2-路归并

归并排序算法的思想是:假设初始列表含有 n 个记录,则可看成 n 个有序的子序列,每个子序列的长度为 1,然后两两归并,得到 个长度为 2 或 1 的有序子序列;再两两归并,··· ···,如此重复,直至得到一个长度为 n 的有序序列为止。

递归实现

class Solution {
public:vector<int> sortArray(vector<int>& nums) {int n = nums.size();vector<int> tmp(n);mergeSort(nums, tmp, 0, n - 1);return nums;}
private:void mergeSort(vector<int>& nums, vector<int>& tmp, int left, int right) {if (left >= right)return;int mid = (left + right) / 2;mergeSort(nums, tmp, left, mid);mergeSort(nums, tmp, mid + 1, right);
​// 合并相邻的两个有序子序列 nums[left···mid] 和 nums[mid+1···right]int i = left, j = mid + 1, k = left;while (i <= mid && j <= right){if (nums[i] <= nums[j])tmp[k++] = nums[i++];elsetmp[k++] = nums[j++];}while (i <= mid){tmp[k++] = nums[i++];}while (j <= right){tmp[k++] = nums[j++];}
​for (int i = left; i <= right; ++i){nums[i] = tmp[i];}}
};

迭代实现

class Solution {
public:vector<int> sortArray(vector<int>& nums) {int n = nums.size();vector<int> tmp(n);for (int seg = 1; seg < n; seg *= 2){for (int left = 0; left < n; left += 2 * seg){int mid = min(left + seg - 1, n - 1);int right = min(left + 2 * seg - 1, n - 1);int i = left, j = mid + 1, k = left;while (i <= mid && j <= right){if (nums[i] <= nums[j])tmp[k++] = nums[i++];elsetmp[k++] = nums[j++];}while (i <= mid){tmp[k++] = nums[i++];}while (j <= right){tmp[k++] = nums[j++];}}nums = tmp;}return nums;}
};

由于长度为 n 的数组每次都被分为两个长度为 n/2 的数组,因此不管输入什么样的数组,归并排序的时间复杂度都是 O(nlog)。归并排序需要创建一个长度为 n 的辅助空间。如果用递归实现归并排序,那么递归的调用栈需要 O(logn) 的空间。因此,归并排序的空间复杂度是 O(n)。

手写归并排序的代码本身就是很常见的面试题,因此,应聘者应深刻理解归并排序的过程,熟悉归并排序的迭代和归并的代码实现。同时,归并排序是应用分治法来解决问题的,类似的思路可以用来解决很多其他的问题。


面试题 77 : 链表排序

题目

输入一个链表的头节点,请将该链表排序。例如,输入下图 (a) 中的链表,该链表排序后如下图 (b) 所示。

分析

可以使用归并排序对链表进行排序,其主要思想是将链表分成两个子链表,在对两个子链表排序之后再将它们合并成一个排序的链表。排序子链表和排序整个链表是同一个问题,可以递归调用同一个函数解决

class Solution {
public:ListNode* sortList(ListNode* head) {if (head == nullptr || head->next == nullptr)return head;ListNode* head1 = head;ListNode* head2 = split(head);
​head1 = sortList(head1);head2 = sortList(head2);
​return merge(head1, head2);}
private:ListNode* split(ListNode* head) {ListNode* slow = head;ListNode* fast = head->next;while (fast && fast->next){slow = slow->next;fast = fast->next->next;}ListNode* secondHead = slow->next;slow->next = nullptr;return secondHead;}
​ListNode* merge(ListNode* head1, ListNode* head2) {ListNode* dummy = new ListNode;ListNode* cur = dummy;while (head1 && head2){if (head1->val <= head2->val){cur->next = head1;head1 = head1->next;}else{cur->next = head2;head2 = head2->next;}cur = cur->next;}cur->next = head1 != nullptr ? head1 : head2;return dummy->next;}
};
  1. 函数 split 将链表分成前后两半,并返回后半部分链表的头节点

    可以用快慢指针的思路将链表分成前后两半,其中慢指针一次走一步,快指针一次走两步

    如果链表的节点总数为偶数,那么当快指针走到链表的尾节点时,慢指针正好走到前半段链表的最后一个节点,前半段链表和后半段链表的节点个数相同

    如果链表的节点总数为奇数,那么当快指针走到空时,慢指针也正好走到前半段链表的最后一个节点,前半段链表比后半段链表多一个节点

  2. 函数 merge 用来合并两个排序的子链表,并返回合并后的排序链表的头节点

    和合并两个排序的子数组类似,也可以用两个指针分别指向两个排序子链表的节点,然后选择其中值较小的节点。与合并数组不同的是,不需要另一个链表来保存合并之后的节点,而只需要调整指针的指向


面试题 78 : 合并排序链表

题目

输入 k 个排序的链表,请将它们合并成一个排序的链表。例如,输入 3 个排序的链表,如下图 (a) 所示,将它们合并之后得到的排序的链表如下图 (b) 所示。

法一、利用最小堆选取值最小的节点

用 k 个指针分别指向这 k 个链表的头节点,从这 k 个节点中选取值最小的节点。然后将指向值最小的节点的指针向后移动一步,再比较 k 个指针指向的节点并选取值最小的节点。重复这个过程,直到所有节点都被选取出来

这思路需要反复比较 k 个节点并选取值最小的节点。既可以每次都用一个 for 循环用 O(k) 的时间复杂度比较 k 个节点的值,也可以将 k 个节点放入一个最小堆中,位于堆顶的节点就是值最小的节点。每当选取某个值最小的节点之后,将它从堆中删除并将它的下一个节点添加到堆中。从最小堆中得到位于堆顶的节点的时间复杂度是 O(1),堆的删除和插入操作的时间复杂度是 O(logk),因此使用最小堆比直观地用 for 循环的时间效率高。

struct Greater {bool operator()(const ListNode* lhs, const ListNode* rhs){return lhs->val > rhs->val;}
};
​
class Solution {
public:ListNode* mergeKLists(vector<ListNode*>& lists) {ListNode* dummy = new ListNode;ListNode* cur = dummy;
​priority_queue<ListNode*, vector<ListNode*>, Greater> minHeap;for (ListNode* head : lists){if (head)minHeap.push(head);}
​while (!minHeap.empty()){cur->next = minHeap.top();minHeap.pop();cur = cur->next;if (cur->next)minHeap.push(cur->next);}return dummy->next;}
};

假设 k 个排序链表总共有 n 个节点。如果堆的大小为 k,那么空间复杂度就是 O(k)。每次用最小堆处理一个节点需要 O(logk) 的时间,因此这种解法的时间复杂度是 O(nlogk)。

法二、按照归并排序的思路合并链表

下面换一种思路来解决这个问题。输入的 k 个排序链表可以分成两部分,前 k/2 个链表和后 k/2 个链表。如果将前 k/2 个链表和后 k/2 个链表分别合并成两个排序的链表,再将这两个排序的链表合并,那么所有链表都合并了。合并 k/2 个链表与合并 k 个链表是同一个问题,可以调用递归函数解决

class Solution {
public:ListNode* mergeKLists(vector<ListNode*>& lists) {if (lists.size() == 0)return nullptr;return mergeTwoList(lists, 0, lists.size() - 1);}
private:ListNode* mergeTwoList(vector<ListNode*>& lists, int left, int right) {if (left == right)return lists[left];int mid = (left + right) / 2;ListNode* head1 = mergeTwoList(lists, left, mid);ListNode* head2 = mergeTwoList(lists, mid + 1, right);
​ListNode* dummy = new ListNode;ListNode* cur = dummy;while (head1 && head2){if (head1->val <= head2->val){cur->next = head1;head1 = head1->next;}else{cur->next = head2;head2 = head2->next;}cur = cur->next;}cur->next = head1 != nullptr ? head1 : head2;return dummy->next;}
};

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

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

相关文章

机器学习-04-分类算法-01决策树案例

总结 本系列是机器学习课程的系列课程&#xff0c;主要介绍机器学习中分类算法&#xff0c;本篇为分类算法开篇与决策树部分。 本门课程的目标 完成一个特定行业的算法应用全过程&#xff1a; 懂业务会选择合适的算法数据处理算法训练算法调优算法融合 算法评估持续调优工程…

redis 缓冲区详解(性能优化缓冲区优化)

目录 前言 客户端输入缓冲区 输出缓冲区 集群缓冲区 全量复制缓冲区问题 增量复制缓冲区问题 前言 在我的《Redis 为啥那么快》这篇文章中&#xff0c;详细总结了Redis 为啥那么快。今天当我要详细阐述Redis 的缓冲区时&#xff0c;意识到应该加上Redis 的缓冲区。我们假…

01_04_JavaWEB03_XML、Tomcat、http

XML_Tomcat10_HTTP 参考尚硅谷再总结复习 一 XML XML是EXtensible Markup Language的缩写&#xff0c;翻译过来就是可扩展标记语言。所以很明显&#xff0c;XML和HTML一样都是标记语言&#xff0c;也就是说它们的基本语法都是标签。 可扩展 三个字表面上的意思是XML允许自定义…

VBA combox/listbox 控件响应鼠标滚轮事件

在vba中&#xff0c;我们在用户窗体中如果添加有combox控件&#xff0c;或者是listbox控件。正常情况下&#xff0c;combox 和 listbox 是不响应鼠标滚轮事件的&#xff0c;且默认的VBA控件中&#xff0c;也没有提供响应鼠标滚轮事件的方法和入口。如此以来&#xff0c;我们在c…

【毕设级项目】基于AI技术的多功能消防机器人(完整工程资料源码)

基于AI技术的多功能消防机器人演示效果 竞赛-基于AI技术的多功能消防机器人视频演示 前言 随着“自动化、智能化”成为数字时代发展的关键词&#xff0c;机器人逐步成为社会经济发展的重要主体之一&#xff0c;“机器换人”成为发展的全新趋势和时代潮流。在可预见的将来&#…

Adobe Photoshop 2024 v25.5.1 for mac 强大的图形编辑工具 兼容 M1/M2/M3

Mac毒搜集到的Adobe Photoshop 2024 v25.5.1 是一款强大的图形编辑和设计工具! v25.5.1版本AI生成式无法使用 应用介绍 Adobe Photoshop 2024是一款强大的图像处理软件&#xff0c;由Adobe公司开发。它可以用于编辑和处理照片、图形和其他类型的图像&#xff0c;包括设计、绘画…

Java双非大二找实习记录

先说结论&#xff1a;2.22→3.6线上线下面了七家&#xff0c;最后oc两家小公司&#xff0c;接了其中一个。 本人bg&#xff1a; 真名不经传双非一本&#xff0c;无绩点无竞赛无奖项无实习&#xff0c;23年12月开始学java。若非要说一点相关的经历&#xff0c;就是有java基础&…

XWPFDocument中XmlCursor的使用

类名&#xff1a; org.apache.xmlbeans Interface XmlCursor版本&#xff1a; 原xml代码&#xff1a; <w:p w14:paraId"143E3662" w14:textId"4167FBA7" w:rsidR"001506F2" w:rsidRPr"003F3D89" w:rsidRDefault"001506F2&qu…

Python3虚拟环境之virtualenv

virtualenv 在开发Python应用程序的时候&#xff0c;系统安装的Python3只有一个版本&#xff1a;3.7。所有第三方的包都会被pip安装到Python3的site-packages目录下。 如果要同时开发多个应用程序&#xff0c;这些应用程序都会共用一个Python&#xff0c;就是安装在系统的Pyt…

【三】安装k8s+kuboard, 拉取harbor镜像并执行yml文件

自己的配置 我在尊云上两百多买了三台2c4g的服务器&#xff0c;其实买两台就够了。 修改服务网卡掩码 确保几台服务器内网之间可以ping通 以尊云为例&#xff0c;vi /etc/sysconfig/network-scripts/ifcfg-eth1 修NETMASK值为255.0.0.0&#xff0c;重启服务器&#xff0c;尝试…

HarmonyOS 关系型数据 整体测试 进行 初始化 增删查改 操作

好啊 前面的文章 HarmonyOS 数据持久化 关系型数据库之 初始化操作 HarmonyOS 数据持久化 关系型数据库之 增删改逻辑编写 HarmonyOS 数据持久化 关系型数据库之 查询逻辑编写 我们分别编写了 初始化数据库表 增删查改操作 的逻辑代码 那么 下面我们就来整体操作一下 然后 这…

C# OpenCvSharp 图片批量改名

目录 效果 项目 代码 下载 C# OpenCvSharp 图片批量改名 效果 项目 代码 using NLog; using OpenCvSharp; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Windows.Forms; namespace OpenCvSharp_Demo { publi…

微信小程序仿QQ头像轮播效果

1、效果图 2、效果流程分析 1、第1个头像大小从1到0缩小的同时&#xff0c;第2个头像左移 2、上面动画结束后&#xff0c;延迟50ms&#xff0c;第3个头像从0到1放大 3、把头像列表顺序前移一位&#xff0c;并重置轮播状态&#xff0c;以此达到一个循环。然后继续第一个步骤 …

Java旋转矩阵

题目&#xff1a; 给你一幅由 N N 矩阵表示的图像&#xff0c;其中每个像素的大小为 4 字节。请你设计一种算法&#xff0c;将图像旋转 90 度。 不占用额外内存空间能否做到&#xff1f; 示例 1: 给定 matrix [ [1,2,3], [4,5,6], [7,8,9] ], 原地旋转输入矩阵&…

wins10安装ffmpeg

官网下载 点击进入官网&#xff1a;ffmpeg&#xff0c;官网地址&#xff1a;https://ffmpeg.org/download.html 点击上图中Windows图标选中后下面显示的第一行进入如下界面&#xff0c;在release builds第一个绿框里面选择一个版本下载&#xff1a; 下载好之后解压后&#xf…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:PatternLock)

图案密码锁组件&#xff0c;以九宫格图案的方式输入密码&#xff0c;用于密码验证场景。手指在PatternLock组件区域按下时开始进入输入状态&#xff0c;手指离开屏幕时结束输入状态完成密码输入。 说明&#xff1a; 该组件从API Version 9开始支持。后续版本如有新增内容&#…

GO语言-切片底层探索(上)

目录 1.前言 2. 算法题目 错误代码 3. 错误分析 4.总结&#xff1a; 5.正确代码&#xff1a; 6.本地测试代码&#xff1a; 1.前言 今天在力扣上写算法&#xff0c;遇到了一个比较"奇怪"的错误。由于自己使用了递归切片&#xff0c;导致一开始没有看明白&…

力扣最热100题——56.合并区间

吾日三省吾身 还记得梦想吗 正在努力实现它吗 可以坚持下去吗 目录 吾日三省吾身 力扣题号&#xff1a;56. 合并区间 - 力扣&#xff08;LeetCode&#xff09; 题目描述 Java解法一&#xff1a;排序然后原地操作 具体代码如下 Java解法二&#xff1a;new一个list&#xf…

虚拟机中安装Win98

文章目录 一、下载Win98二、制作可启动光盘三、VMware中安装Win98四、Qemu中安装Win981. Qemu的安装2. 安装Win98 Win98是微软于1998年发布的16位与32位混合的操作系统&#xff0c;也是一代经典的操作系统&#xff0c;期间出现了不少经典的软件与游戏&#xff0c;还是值得怀念的…

安卓多个listView拖动数据交换位置和拖动

注意这里只是给出大概思路&#xff0c;具体可以参考修改自己想要的 public class MainActivity extends AppCompatActivity {private ListView listView1;private ListView listView2;private ArrayAdapter<String> adapter1;private ArrayAdapter<String> adapter…