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

这篇具有很好参考价值的文章主要介绍了【C/C++练习】合并k个已排序的链表。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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


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

🐻题目描述:

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

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

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

 首先根据题目的描述,画出如下模拟图。
【C/C++练习】合并k个已排序的链表

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

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

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

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

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

 总体逻辑就是这样,接下来循环执行第二步,找到次小的进行尾插,直到数组中的所有节点都为空,此时说明数组中的所有链表都已经排好序了,返回rhead即可。总的过程动图如下:
【C/C++练习】合并k个已排序的链表

🐼代码实现:

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个有序链表的问题,划分成合并两个有序链表的问题,具体过程我通过动图来给大家演示,其中相同的颜色代表一个子问题
【C/C++练习】合并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,然后我们只需要对这两个链表进行合并就可以完成题目的要求,也就是第三行代码实现的功能。这就是分治的思想


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

【C/C++练习】合并k个已排序的链表文章来源地址https://www.toymoban.com/news/detail-490309.html

到了这里,关于【C/C++练习】合并k个已排序的链表的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 2、有序链表的维护【问题描述】编写一个程序,根据从标准输入接收的指令来维护和操作排序的链表(C语言、java和Python分别实现)

    【问题描述】 编写一个程序,根据从标准输入接收的指令来维护和操作排序的链表。链表是按顺 序维护的,这意味着链表中的数据在每次操作后都以递增的数字顺序存储。 请注意,在创建新节点时,需要使用malloc为它们分配空间;一旦不再需要任何已 分配的空间,就应该使

    2024年02月09日
    浏览(34)
  • 面试算法78:合并排序链表

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

    2024年02月03日
    浏览(27)
  • 【算法入门&链表】【模板】链表|反转链表|合并排序链表|删除链表的节点

    ✅作者简介:热爱后端语言的大学生,CSDN内容合伙人 ✨精品专栏:C++面向对象 🔥系列专栏:算法百炼成神 本专栏收录的均为牛客网的算法题目,内含链表、双指针、递归、动态规划、基本数据结构等算法思想的具体运用。牛客网不仅有大量的经典算法题目,也有大厂的面

    2024年02月17日
    浏览(34)
  • 【Leetcode -21.合并两个有序链表 -83.删除排序链表中的重复元素】

    题目:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1: 输入:l1 = [1, 2, 4], l2 = [1, 3, 4] 输出:[1, 1, 2, 3, 4, 4] 示例 2: 输入:l1 = [], l2 = [] 输出:[] 示例 3: 输入:l1 = [], l2 = [0] 输出:[0] 我们的思路是,先定义

    2023年04月24日
    浏览(35)
  • 【链表OJ 11】复制带随机指针的链表

    前言:  💥🎈个人主页:​​​​​​Dream_Chaser~ 🎈💥 ✨✨刷题专栏:http://t.csdn.cn/UlvTc ⛳⛳本篇内容:力扣上链表OJ题目 目录 leetcode138. 复制带随机指针的链表 1. 问题描述 2.代码思路: 2.1拷贝节点插入到原节点的后面 2.2控制拷贝节点的random     2.3拷贝节点解下来尾插组成拷

    2024年02月09日
    浏览(30)
  • 探究Java中的链表

    引言:         在Java编程中,链表是一种常见的数据结构,具有灵活的内存管理和动态的元素插入与删除能力。本篇博客将深入探讨链表的结构和概念,比较链表与顺序表的区别,介绍Java中LinkedList的常用函数并通过示例说明LinkedList的使用。         链表是一种线性

    2024年01月20日
    浏览(29)
  • Java中的链表

    上一节中,我们讲过了Java中的ArrayList,当 在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n) ,效率比较低,因此 ArrayList不适合做任意位置插入和删除比较多的场景 。因此:java集合中又引入了LinkedList,即链表结构。 链表是

    2024年02月15日
    浏览(31)
  • 算法:常见的链表算法

    本篇总结常见的链表算法题和看他人题解所得到的一些收获 关于链表的算法: 画图:画图可以解决绝大部分的数据结构的问题,任何的算法题借助图都可以有一个比较清晰的思路 引入哨兵位头节点:头节点可以避免处理很多边界情况,在解决一些问题前很方便 多定义几个变

    2024年02月22日
    浏览(30)
  • 138. 复制带随机指针的链表

    给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应

    2024年02月13日
    浏览(44)
  • 力扣-复制带随机指针的链表

    给你一个长度为  n  的链表,每个节点包含一个额外增加的随机指针  random  ,该指针可以指向链表中的任何节点或空节点。 构造这个链表的  深拷贝 。 深拷贝应该正好由  n  个  全新  节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的  next  指针和

    2024年02月11日
    浏览(27)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包