【C/C++练习】合并k个已排序的链表

news2025/1/11 20:07:11

在这里插入图片描述

在这里插入图片描述

目录

  • 🐻题目描述:
  • 🐻‍❄️思路一:暴力求解法
    • 🐼第一步:确定合并后链表的头节点rhead
    • 🐼第二步:选择次小的进行尾插
    • 🐼代码实现:
  • 🐻‍❄️思路二:分治归并法
    • 🐼代码实现:

前言:
 今天给大家分享一道面试中常见的题目——合并K个升序链表,我会用暴力和分治两钟方法去求解这道题目,通过动图展示问题求解的全过程。这里提醒大家,画图是我们求解复杂问题的有效手段,有时可以起到事半功倍的效果,各位小伙伴在做题的过程中如果遇到麻烦,不妨动手画画图哟。

🐻题目描述:

 合并K个升序的链表并将结果作为一个升序的链表返回其头节点。例如:

  • 输入:[{1,2},{1,4,5},{6},{2,3,5}]
  • 输出:{1,1,2,2,3,4,5,5,6}

🐻‍❄️思路一:暴力求解法

 首先根据题目的描述,画出如下模拟图。
在这里插入图片描述

🐼第一步:确定合并后链表的头节点rhead

 从上图中可以看出:lists中存放是每个链表的头节点,那合并后链表的头节点一定是这四个头结点中最小的那个,因此我们只需要遍历lists就可以找到最小的头节点,然后把它赋值给rhead,执行完第一步得到的结果如下图,用黄色标注已经排好序的节点:
在这里插入图片描述

🐼第二步:选择次小的进行尾插

在这里插入图片描述

 如上图,接下来我们需要在所有蓝色节点中选出最小的一个尾插到rhead指向的链表,因此我们再定义一个rtail指向合并后链表的最后一个节点。但是我们发现如果按照上图的结构,直接从四个蓝色节点里面选出最小的,显然十分困难, 因为第一个链表中待比较的节点不再数组中。如果向第一步那样,所有的节点都在数组中,那选出最小的节点简直是易如反掌,只需要遍历一遍数组即可。因为lists数组中存的永远都是头节点,所以这里我们直接修改第一个链表的头节点即可,通过lists[0] = lists[0]->next就可以实现。修改后的结构如下图所示:
在这里插入图片描述
 此时所有待比较的节点都来到了数组中,和第一步的逻辑一样,只需要遍历一遍数组就可以找到最小的节点,找到后尾插到rhead指向的链表。如下图,其中黄色节点是已排好序的节点,蓝色节点是待比较的节点。
在这里插入图片描述

 总体逻辑就是这样,接下来循环执行第二步,找到次小的进行尾插,直到数组中的所有节点都为空,此时说明数组中的所有链表都已经排好序了,返回rhead即可。总的过程动图如下:
在这里插入图片描述

🐼代码实现:

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        ListNode* rhead = nullptr;//定义合并后链表的头节点
        ListNode* rtail = nullptr;//定义合并后链表的尾结点
        while (true)
        {
            int minIndex = -1;//定义lists数组中最小节点下标,最初等于-1,(下简称最小位置)
            int i = lists.size();//
            while (i--)
            {
                if (lists[i] != nullptr)//首先判断数组当前位置的节点是否为空
                {
                    //当前节点不为空再进来
                    if (minIndex == -1)//判断最小位置是否是-1,如果是就直接把当前位置赋值给minIndex
                    {
                        minIndex = i;
                    }
                    else if (lists[i]->val < lists[minIndex]->val)//如果minIndex不为-1,则用数组当前位置节点的val与记录的最小位置上节点的val进行比较
                    {
                        minIndex = i;//如果当前节点的val值更小,那就更新minIndex
                    }
                }
            }
            if (minIndex == -1)//遍历完一遍数组如果minIndex的值还是-1,说明当前数组中的所有节点全是空
            {
                return rhead;//此时说明所有链表已经合并完成,可以返回头节点
            }
            if (rhead == nullptr)//确定新链表的头节点
            {
                rhead = rtail = lists[minIndex];
            }
            else//之后每找出一个最小的节点,进行尾插即可
            {
                rtail->next = lists[minIndex];
                rtail = rtail->next;
            }
            lists[minIndex] = lists[minIndex]->next;//最小的节点已经尾插到新链表,因此要对最小位置上的节点更新
        }
    }
};

 上面代码中需要注意的有:minIndex 是用来记录lists中最小节点的位置,它的初始值必须是-1,不能是0或其他数字,因为我们不知道0或其他位置上的节点是否为空。还有需要注意的地方是,每当遍历到一个节点,首先要判断是否为nullptr,当不为空的时候再进行比较,避免出现空指针问题,其次要判断minIndex当前是否为-1,如果是-1,就不用比较,直接把当前位置赋值给minIndex 即可,因为如果当minIndex == -1 的时候进行比较,会导致数组越界。

🐻‍❄️思路二:分治归并法

 分治即“分而治之”,“分”指的是将一个大而复杂的问题划分成多个性质相同但是规模更小的问题,子问题继续按照这样划分,直到问题可以被轻易解决;“治”指的是将子问题单独进行处理。经过分治后的子问题,需要将解进行合并,也就是所谓的“归并”,这样才能得到原问题的解,因此整个分支过程经常采用递归来实现。
 针对这道题目来说,我们比较熟悉的是合并两个有序链表,因此我们就可以把合并K个有序链表的问题,划分成合并两个有序链表的问题,具体过程我通过动图来给大家演示,其中相同的颜色代表一个子问题
在这里插入图片描述
 通过上面的动图我们可以发现,当子问题被分解到只剩一个链表的时候,就无法再进行分解,此时就需要返回了,其实这就是递归的终止条件,也就是当left == right的时候就只剩一个链表,此时就应该返回了,返回的结果就是这一个链表。等左右区间各返回一个链表,此时就要开始对这两个链表进行合并,从而得到一个新的有序链表,再把这个链表作为子问题的处理结果进行返回。这其实很像二叉树的后序遍历,如此循环往复,直到最终的大问题被解决。

🐼代码实现:

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        return dividemerge(lists, 0, lists.size() - 1);
    }
private:
    ListNode* dividemerge(vector<ListNode*>& lists, int left, int right)
    {
        if (left > right)
        {
            return nullptr;
        }
        else if (left == right)
        {
            return lists[left];
        }

        int mid = left + (right - left) / 2;
        //分治
        ListNode* head1 = dividemerge(lists, left, mid);
        ListNode* head2 = dividemerge(lists, mid + 1, right);

        //对子问题处理
        return Merge(head1, head2);
    }
private:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)//合并两个有序链表
    {
        if (pHead1 == nullptr)
        {
            return pHead2;
        }
        if (pHead2 == nullptr)
        {
            return pHead1;
        }
        ListNode* pHeadret, * tail;
        if (pHead1->val < pHead2->val)
        {
            pHeadret = tail = pHead1;
            pHead1 = pHead1->next;
        }
        else
        {
            pHeadret = tail = pHead2;
            pHead2 = pHead2->next;
        }
        while (pHead1 != nullptr && pHead2 != nullptr)
        {
            if (pHead1->val < pHead2->val)
            {
                tail->next = pHead1;
                pHead1 = pHead1->next;
            }
            else
            {
                tail->next = pHead2;
                pHead2 = pHead2->next;
            }
            tail = tail->next;
        }
        if (pHead2 != nullptr)
        {
            tail->next = pHead2;
        }
        if (pHead1 != nullptr)
        {
            tail->next = pHead1;
        }
        return pHeadret;
    }
};

 分治的大思想,其实就体现在下面三行代码中:

//分治
ListNode* head1 = dividemerge(lists, left, mid);        
ListNode* head2 = dividemerge(lists, mid + 1, right);

//对子问题处理        
return Merge(head1, head2);

 我们只需要知道,前两行代码将一个大问题进行分解,得到的两个子问题,并且经过dividemerge函数把这两个子问题都处理好了,并且得到了两个结果,针对本题,就是得到了两个有序的链表,分别是head1head2,然后我们只需要对这两个链表进行合并就可以完成题目的要求,也就是第三行代码实现的功能。这就是分治的思想


 今天的分享到这里就结束啦,原题链接放在这里,感兴趣的小伙伴可以自己去试试。以上的内容如果对你有帮助的话,可以动动小手点赞、评论、收藏哟,您的支持就是我前进路上最大的动力❤️🥰!

在这里插入图片描述

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

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

相关文章

《C++继承》

本文主要介绍继承的相关知识 文章目录 思维导图一、继承的概念及定义1.继承的概念2.继承的定义2.1 定义格式2.2 继承关系和访问限定符2.3 继承基类成员访问方式的变化 二、基类和派生类对象的赋值转换三、继承中的作用域四、派生类的默认成员函数五、继承与友元六、继承与静态成…

Vue中的Ajax 配置代理 slot插槽

4.1.Vue脚手架配置代理 本案例需要下载axios库npm install axios 配置参考文档Vue-Cli devServer.proxy vue.config.js 是一个可选的配置文件&#xff0c;如果项目的 (和 package.json 同级的) 根目录中存在这个文件&#xff0c;那么它会被 vue/cli-service 自动加载。你也可…

【OJ比赛日历】快周末了,不来一场比赛吗? #06.17-06.23 #13场

CompHub[1] 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 以下信息仅供参考&#xff0c;以比赛官网为准 目录 2023-06-17&#xff08;周六&#xff09; #2场比赛2023-06-18…

使用matplotlib制作动态图

使用matplotlib制作动态图 一、简介二、模块简介1. **FuncAnimation**类介绍2. 定义动画更新函数 三、使用matplotlib制作动画1.一步法制作动态图片2. 两步法制作动态图片 一、简介 matplotlib(https://matplotlib.org/)是一个著名的python绘图库&#xff0c;由于其灵活强大的…

【Qt】使用libmodbus

这里写目录标题 下载编译使用Demo参照&#xff1a; Qt自带QModbusTcpClient&#xff0c;换个电脑就不好使了&#xff0c;换libmodbus 下载 可以去github下载 链接: https://pan.baidu.com/s/13lgEZ59Dt5M7zmTJNpfKvg?pwdyzfm 提取码: yzfm 下载libmodbus 并解压 编译 进入…

20分钟做一套采购审批系统

1、设计输入模板 excel画表格界面 # 公式代表新建时以默认值代替 2、设置单元格为签名控件 双击单元格后&#xff0c;会默认显示当前用户的信息,用于签名 3、设置要合计的数据 生成的合计公式会默认放到下一行 4、设置单元格的ID与标题&#xff0c;在添加或者删除行或者列时&am…

影像组学技术的基础和应用

一、概述 1. 影像组学足迹史 2003年&#xff0c;Mark A. Kriegsman和Randy L. Buckner发表的关于视觉系统空间组织的研究文章The genetic and functional basis of the spatial organization of the visual system&#xff0c;为影像组学领域提供了先驱性思路&#xff0c;奠定…

PID算法:过程控制中的重要质量指标

PID算法&#xff1a;过程控制中几个重要的概念 PID算法广泛的被应用在很多的控制系统中&#xff0c;最终的目的都是希望通过pid控制器实现被控量能稳定在预期的目标值。 使用pid控制器作用于系统的时候&#xff0c;正常情况下它应该是不断的发生作用的&#xff0c;从而让系统能…

OJ Goldbach‘s Conjecture

1.题目 题目描述 In 1742, Christian Goldbach, a German amateur mathematician, sent a letter to Leonhard Euler in which he made the following conjecture: Every even number greater than 4 can be written as the sum of two odd prime numbers. For exampl…

可调电源LM317 的内部原理 - 特殊的电压跟随器

之前一直没想过这类LDO 内部是怎么整的&#xff0c;它似乎是用一个分压电路采集它输出的电压作为参考&#xff0c;然后却能把输出电压稳定下来&#xff0c;颇有种左脚踩右脚上天的意思。典型的LM317 电路如下&#xff1a; 如果是个普通的电压跟随器&#xff0c;无论是基于三极管…

K8S之istio流量控制管理(十七)

一&#xff0c;istio介绍 1、istio架构 结合上图我们来理解Istio的各组件的功能及相互之间的协作方式。 1. 自动注入&#xff1a;在创建应用程序时自动注入 Sidecar代理Envoy程序。在 Kubernetes中创建 Pod时&#xff0c;Kube-apiserver调用控制面组件的 Sidecar-Injector服…

4年外包上岸,想劝大家:这类公司能不去就不去...

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

Vector-常用CAN工具 - Network-Based Access常见问题

目录 一、什么是基于网络的访问&#xff1f; 二、为什么是基于网络的访问&#xff1f; 三、Channel-based如何变更为Network-based 四、VN5000系列设备端口分配 五、常见问题及解决办法 如何导出以太网的设备配置&#xff1f;&#xff08;Network-Base&#xff09; 1、导…

Backbone 在神经网络中意味着什么?

动动发财的小手&#xff0c;点个赞吧&#xff01; 1. 简介 神经网络是机器学习算法&#xff0c;我们可以将其用于许多应用&#xff0c;例如图像分类、对象识别、预测复杂模式、处理语言等等。神经网络的主要组成部分是层和节点。 一些神经网络架构有一百多个层和几个解决不同子…

[CubeMX项目]基于STM32的平衡小车(硬件设计)

一直以来我都想在本科毕业前完成一个电机相关的实验&#xff0c;之前看了网上比较火热的自平衡莱洛三角形项目后&#xff0c;决心先做一个类似的小项目。因此&#xff0c;我通过学习大量前辈的项目案例&#xff0c;完成了该项目。 本项目的特点是&#xff1a;在需要通信的部分&…

CMU 15-445 Project #1 - Buffer Pool(Task #3 - Buffer Pool Manager Instance)

Task #3 - Buffer Pool Manager Instance 一、题目链接二、准备工作三、部分实现 一、题目链接 二、准备工作 见 CMU 15-445 Project #0 - C Primer 中的准备工作。 三、部分实现 首先要区分缓冲池中 Page 与 Frame &#xff0c;这个其实和操作系统分页管理中页面和页框的关系…

尚硅谷微信小程序开发 防网易云音乐App 小程序 后端接口服务器搭建

小程序学习 尚硅谷微信小程序开发 项目网易云小程序学习地址&#xff1a; 01-尚硅谷-小程序-课程介绍_哔哩哔哩_bilibili 视频相关的教程文档与笔记分享 链接&#xff1a;https://pan.baidu.com/s/1aq7ks8B3fJ1Wahge17YYUw?pwd7oqm 提取码&#xff1a;7oqm 配套服务器 老师…

C语言总结

C语言 预处理&#xff08;以#开头&#xff09; 宏定义 宏可以理解为替换&#xff0c;替换过程不会进行语法检查&#xff0c;语法检查在编译时进行。只替换只替换只替换 1.不带参数的宏定义&#xff1a; 宏定义又称为宏代换、宏替换&#xff0c;简称“宏”。实质为直接替换&…

java面经03-虚拟机篇-jvm内存结构垃圾回收、内存溢出类加载、引用悲观锁HashTable、引用finalize

文章目录 虚拟机篇1. JVM 内存结构2. JVM 内存参数3. JVM 垃圾回收4. 内存溢出5. 类加载6. 四种引用7. finalize 虚拟机篇 1. JVM 内存结构 要求 掌握 JVM 内存结构划分尤其要知道方法区、永久代、元空间的关系 结合一段 java 代码的执行理解内存划分 执行 javac 命令编译源…

力扣 2719. 统计整数数目

题目地址&#xff1a;https://leetcode.cn/problems/count-of-integers/ 递归核心是枚举统计&#xff0c;结合记忆化搜索节省时间。 以数字 3216 为例&#xff0c;从 [0, 0, 0, 0] 开始枚举&#xff0c;到 [2, 1, 6, X] 时&#xff0c;i 2&#xff0c;sum 2 1 6 9&#x…