OJ题-合并K个已排序的链表

news/2024/9/19 3:03:00 标签: 链表, 数据结构

描述

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

数据范围:节点总数 0≤n≤50000≤n≤5000,每个节点的val满足 ∣val∣<=1000∣val∣<=1000

要求:时间复杂度 O(nlogn)O(nlogn)

下面给出C++代码的两种方法:

方法1:使用优先队列(最小堆)

优先队列可以很方便地处理多个链表头节点的最小值,每次从 K链表中取出最小的节点,合并到结果链表中。

思路:

  1. 使用优先队列(最小堆)来存储每个链表的当前头节点。
  2. 每次从堆中取出最小值节点,并将它的下一个节点重新加入堆。

重复这个过程,直到所有链表都被合并。

#include <queue>
#include <vector>
#include <iostream>

struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(nullptr) {}
};

// 比较器,用于优先队列
struct Compare {
    bool operator()(ListNode* a, ListNode* b) {
        return a->val > b->val;
    }
};

class Solution {
public:
    ListNode* mergeKLists(std::vector<ListNode*>& lists) {
        // 使用优先队列(最小堆)
        std::priority_queue<ListNode*, std::vector<ListNode*>, Compare> minHeap;
        
        // 初始化:将每个链表的头节点加入优先队列
        for (auto node : lists) {
            if (node) {
                minHeap.push(node);
            }
        }
        
        // 哑节点,便于构建新链表
        ListNode* dummy = new ListNode(0);
        ListNode* cur = dummy;
        
        // 逐步从最小堆中取出最小的节点,并更新链表
        while (!minHeap.empty()) {
            // 取出最小值节点
            ListNode* minNode = minHeap.top();
            minHeap.pop();
            
            // 将最小节点接到新链表的末尾
            cur->next = minNode;
            cur = cur->next;
            
            // 如果最小节点的下一个节点不为空,将其加入堆中
            if (minNode->next) {
                minHeap.push(minNode->next);
            }
        }
        
        // 返回合并后的链表
        return dummy->next;
    }
};

分析:

1、优先队列的使用

  • 优先队列使用自定义比较器 Compare,使得最小的节点始终位于堆顶。
  • 每次从堆顶取出最小的节点,并将它的 next 节点放入堆中。

2、时间复杂度O(N log K),其中 N 是所有链表节点的总数,K链表的个数。每次从优先队列取出最小元素的时间复杂度为 O(log K),一共需要执行 N 次。

3、空间复杂度O(K),优先队列最多存储 K 个元素。

方法2:分治法

可以使用分治法逐步将 K链表两两合并,类似于归并排序的过程。每次两两合并直到只剩一个链表

思路:

  1. K链表分成两部分,分别合并。
  2. 不断递归分治,直到只剩下两个链表,将它们合并。
  3. 最终得到合并后的链表
    class Solution {
    public:
        // 合并两个有序链表
        ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
            if (!l1) return l2;
            if (!l2) return l1;
            
            if (l1->val < l2->val) {
                l1->next = mergeTwoLists(l1->next, l2);
                return l1;
            } else {
                l2->next = mergeTwoLists(l1, l2->next);
                return l2;
            }
        }
        
        // 分治法合并 K 个链表
        ListNode* mergeKLists(std::vector<ListNode*>& lists) {
            if (lists.empty()) return nullptr;
            return mergeKListsHelper(lists, 0, lists.size() - 1);
        }
        
        // 分治递归函数
        ListNode* mergeKListsHelper(std::vector<ListNode*>& lists, int left, int right) {
            if (left == right) return lists[left];
            int mid = left + (right - left) / 2;
            ListNode* l1 = mergeKListsHelper(lists, left, mid);
            ListNode* l2 = mergeKListsHelper(lists, mid + 1, right);
            return mergeTwoLists(l1, l2);
        }
    };
    

    分析:

1、分治递归

  • 使用递归将链表划分为两部分,每次递归调用 mergeKListsHelper 合并两部分链表,直到只剩下两个链表
  • 通过 mergeTwoLists 函数合并两个链表

2、时间复杂度O(N log K),其中 N 是所有节点的总数,K链表的个数。分治法的递归树的高度为 log K,每次合并的时间复杂度为 O(N)

3、空间复杂度O(log K),由于递归调用栈的深度为 log K

总结:

优先队列法适合需要立即获取最小节点的情况,效率较高。

分治法适合逐步合并的方式,利用了归并排序的思想,递归实现较为直观。


http://www.niftyadmin.cn/n/5664896.html

相关文章

机器学习中求解模型参数的方法

机器学习中用于求解模型参数的方法主要包括以下几种&#xff1a; 极大似然估计&#xff08;Maximum Likelihood Estimation, MLE&#xff09;&#xff1a;这是一种最常见的参数估计方法。目标是找到一组参数&#xff0c;使得在这组参数下&#xff0c;观察到当前样本数据的概率最…

Delphi Web和Web服务开发目前有哪些选择

Delphi Web和Web服务开发目前有哪些选择 Delphi Web和Web服务开发目前有以下几个选择&#xff1a; Delphi MVC Framework&#xff08;https://github.com/delphimvcframework/delphimvcframework&#xff09;&#xff1a;这是一个开源的Delphi Web框架&#xff0c;基于MVC&am…

【python版】示波器输出的csv文件(时间与电压数据)如何转换为频率与幅值【方法①】

示波器输出的csv文件中有两列数据&#xff0c;分别为时间与电压数据&#xff0c;如何将两列数据转换为频率与幅值数据&#xff0c;这涉及到信号的频谱分析&#xff0c;通常通过快速傅里叶变换&#xff08;FFT&#xff09;实现。以下是逐步的详细说明&#xff1a; 1、准备工作 …

数据库事务的详解

1、 介绍 什么是事务? 事务是一个原子操作。是一个最小执行单元。可以由一个或多个SQL语句组成&#xff0c;在同一个事务当中&#xff0c;所有的SQL语句都成功执行时&#xff0c;整个事务成功&#xff0c;有一个SQL语句执行失败&#xff0c;整个事务都执行失败。(一组操作同时…

英飞凌PSoC4000T示例工程

关于PSoC4000T的初步介绍见:英飞凌MCU第五代高性能CAPSENSE技术PSoC4000T_psoc 4000t-CSDN博客 下面这个工程,在modustoolbox中可编译、下载到开发板、debug调试。 编译时会用到mtb_shared这个库: 已经pdl这个periperal driver library库:

继承1 2024_9_18

1.继承的基本用法 当需要继承的时候,我们就在派生类的后面加上一个权限父类,这个权限可以是公有,保护和私有,后面就是继承的父类.此时,下面的stu这个派生类,也就可以使用Person里面的方法了. 2.继承基类成员访问方式的变化 当父类被继承到派生类的时候,此时会根据继承方式的不…

【算法竞赛】队列

队列相关概念 队列中的数据存取方式是“先进先出”,只能向队尾插入数据,从队头移出数据. 队列的原型在生活中很常见,如食堂打饭的队伍,先到先服务.队列有两种实现方式:链队列和循环队列,如图1.2所示. 链队列可以看作单链表的一种特殊情况,用指针把各个节点连接起来. 循环队…

QCustomPlot笔记(一)

文章目录 简介将帮助文档添加到Qt Creator中编译共享库cmake工程编译提示ui_mainwindow.h找不到qcustomplot.h文件 环境:windowsQt Creator 10.0.1cmake 简介 QT中用于绘制曲线的第三方工具 下载地址&#xff1a;https://www.qcustomplot.com/index.php/download 第一个压缩…