news 2026/7/7 18:05:00

【每日算法】LeetCode 23. 合并 K 个升序链表

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【每日算法】LeetCode 23. 合并 K 个升序链表

对前端开发者而言,学习算法绝非为了“炫技”。它是你从“页面构建者”迈向“复杂系统设计者”的关键阶梯。它将你的编码能力从“实现功能”提升到“设计优雅、高效解决方案”的层面。从现在开始,每天投入一小段时间,结合前端场景去理解和练习,你将会感受到自身技术视野和问题解决能力的质的飞跃。------ 算法:资深前端开发者的进阶引擎

LeetCode 23. 合并 K 个升序链表

1. 题目描述

给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,并返回合并后的链表。

示例 1:

输入:lists = [[1,4,5],[1,3,4],[2,6]] 输出:[1,1,2,3,4,4,5,6] 解释:链表数组如下: [ 1->4->5, 1->3->4, 2->6 ] 将它们合并到一个有序链表中得到。 1->1->2->3->4->4->5->6

示例 2:

输入:lists = [] 输出:[]

示例 3:

输入:lists = [[]] 输出:[]

提示:

  • k == lists.length
  • 0 <= k <= 10^4
  • 0 <= lists[i].length <= 500
  • -10^4 <= lists[i][j] <= 10^4
  • lists[i]升序排列
  • lists[i].length的总和不超过10^4

2. 问题分析

在前端开发中,我们经常需要处理多个有序数据流的合并,例如:

  1. 多来源数据聚合:从多个API接口获取已排序的数据,需要合并展示
  2. 日志合并:多个服务的按时间排序的日志需要合并分析
  3. 虚拟列表优化:多个有序数据源合并后渲染长列表

这个问题本质上是多路归并问题,是归并排序的扩展。对于前端开发者来说,理解此问题有助于掌握分治、优先队列等思想,在处理大数据流、实现高效渲染时非常有用。

3. 解题思路

3.1 思路概览

我们有几种主要解决方案:

  1. 顺序合并法:依次合并每个链表
  2. 分治法:借鉴归并排序思想,两两合并
  3. 优先队列(最小堆)法:维护当前所有链表头节点的最小值
  4. 暴力解法:将所有节点值收集后排序,再构建链表

从复杂度分析看,优先队列法分治法是最优解,时间复杂度都是O(Nlogk),其中N是总节点数,k是链表数量。

3.2 各思路详解

3.2.1 顺序合并法

逐个链表合并,每次合并两个有序链表。简单直观,但效率较低。

3.2.2 分治法

采用归并排序的思想,将k个链表配对并两两合并,重复这一过程直到合并成一个链表。

3.2.3 优先队列法(最优解之一)

维护一个大小为k的最小堆,每次从堆中取出最小节点,将该节点的下一个节点加入堆中,直到堆为空。

3.2.4 暴力解法

收集所有节点值到数组,排序后构建新链表。简单但失去了链表的特性优势。

4. 各思路代码实现

4.1 顺序合并法

/** * 合并两个有序链表 */constmergeTwoLists=(l1,l2)=>{constdummy=newListNode(0);letcur=dummy;while(l1&&l2){if(l1.val<l2.val){cur.next=l1;l1=l1.next;}else{cur.next=l2;l2=l2.next;}cur=cur.next;}cur.next=l1||l2;returndummy.next;};/** * 顺序合并K个链表 */constmergeKLists=function(lists){if(lists.length===0)returnnull;letresult=lists[0];for(leti=1;i<lists.length;i++){result=mergeTwoLists(result,lists[i]);}returnresult;};

4.2 分治法

/** * 分治法合并K个链表 */constmergeKLists=function(lists){if(lists.length===0)returnnull;constmerge=(start,end)=>{if(start===end)returnlists[start];if(start>end)returnnull;constmid=Math.floor((start+end)/2);constleft=merge(start,mid);constright=merge(mid+1,end);returnmergeTwoLists(left,right);};returnmerge(0,lists.length-1);};/** * 迭代版本的分治法 */constmergeKListsIterative=function(lists){if(lists.length===0)returnnull;letinterval=1;constn=lists.length;while(interval<n){for(leti=0;i<n-interval;i+=interval*2){lists[i]=mergeTwoLists(lists[i],lists[i+interval]);}interval*=2;}returnlists[0];};

4.3 优先队列法(最小堆)

/** * 优先队列法(最小堆实现) */classMinHeap{constructor(){this.heap=[];}size(){returnthis.heap.length;}push(node){this.heap.push(node);this.bubbleUp(this.heap.length-1);}pop(){if(this.size()===0)returnnull;constmin=this.heap[0];constlast=this.heap.pop();if(this.size()>0){this.heap[0]=last;this.sinkDown(0);}returnmin;}bubbleUp(index){constnode=this.heap[index];while(index>0){constparentIndex=Math.floor((index-1)/2);constparent=this.heap[parentIndex];if(node.val>=parent.val)break;this.heap[parentIndex]=node;this.heap[index]=parent;index=parentIndex;}}sinkDown(index){constlength=this.size();constnode=this.heap[index];while(true){letleftChildIndex=2*index+1;letrightChildIndex=2*index+2;letswap=null;letleftChild,rightChild;if(leftChildIndex<length){leftChild=this.heap[leftChildIndex];if(leftChild.val<node.val){swap=leftChildIndex;}}if(rightChildIndex<length){rightChild=this.heap[rightChildIndex];if((swap===null&&rightChild.val<node.val)||(swap!==null&&rightChild.val<leftChild.val)){swap=rightChildIndex;}}if(swap===null)break;this.heap[index]=this.heap[swap];this.heap[swap]=node;index=swap;}}}constmergeKLists=function(lists){if(lists.length===0)returnnull;constminHeap=newMinHeap();constdummy=newListNode(0);letcur=dummy;// 将所有链表的头节点加入最小堆for(letlistoflists){if(list){minHeap.push(list);}}// 不断从堆中取出最小节点while(minHeap.size()>0){constnode=minHeap.pop();cur.next=node;cur=cur.next;// 如果该节点还有下一个节点,加入堆中if(node.next){minHeap.push(node.next);}}returndummy.next;};/** * 使用JavaScript内置的优先队列(如果环境支持) */constmergeKListsWithPriorityQueue=function(lists){if(lists.length===0)returnnull;constdummy=newListNode(0);letcur=dummy;// 使用优先队列,按节点值排序constpq=newPriorityQueue({compare:(a,b)=>a.val-b.val});// 初始化优先队列for(letlistoflists){if(list){pq.enqueue(list);}}// 处理队列while(!pq.isEmpty()){constnode=pq.dequeue();cur.next=node;cur=cur.next;if(node.next){pq.enqueue(node.next);}}returndummy.next;};

4.4 暴力解法

/** * 暴力解法 */constmergeKLists=function(lists){constnodes=[];// 收集所有节点值for(letlistoflists){while(list){nodes.push(list.val);list=list.next;}}// 排序nodes.sort((a,b)=>a-b);// 构建新链表constdummy=newListNode(0);letcur=dummy;for(letvalofnodes){cur.next=newListNode(val);cur=cur.next;}returndummy.next;};

5. 各实现思路的复杂度、优缺点对比

方法时间复杂度空间复杂度优点缺点适用场景
顺序合并O(kN)O(1)实现简单,无需额外空间效率低,重复遍历节点多k较小或链表长度较短
分治法O(Nlogk)O(logk)效率高,递归思路清晰递归栈空间开销通用场景,尤其适合链表数量多
优先队列O(Nlogk)O(k)效率高,逻辑清晰需要维护堆结构实时数据流处理,k较大
暴力解法O(NlogN)O(N)实现极其简单破坏链表结构,额外空间大快速实现,不关心性能

说明:

  • N:所有链表的总节点数
  • k:链表数量
  • 最优解:分治法和优先队列法都是最优时间复杂度,优先选择
  • 前端场景推荐:优先队列法,逻辑清晰且易于理解和维护

6. 总结

6.1 核心要点

  1. 问题本质:多路归并问题,是归并排序从两路到多路的扩展
  2. 关键思想:分治与优先队列(堆)是解决此类问题的核心思想
  3. 最优复杂度:O(Nlogk),无法再优化,因为每个节点都需要处理,且每次选择最小值需要logk时间

6.2 实际应用场景

  1. 前端数据处理

    • 多个API返回的有序数据合并展示
    • 多来源日志的时间线合并
    • 搜索引擎的多索引结果合并
  2. 性能优化

    • 虚拟滚动列表的数据流合并
    • 大文件分片下载后的有序合并
    • 实时聊天消息的多会话合并
  3. 系统设计

    • 多个有序数据源的流式处理
    • 分布式系统中的有序日志合并
    • 时间序列数据库的多查询结果合并
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/7 9:53:30

针对安卓设备的攻击

1. 中间人攻击 由于用户经常连接公共wi-fi,中间人攻击是针对移动设备最常见的攻击之一。如果能对设备发起中间人攻击,不仅导致在用户连接不安全的网络时将数据提供给攻击者,在某些情况下还可能让攻击者篡改用户的通讯数据并利用漏洞。webview的addjavascriptInterface漏洞就…

作者头像 李华
网站建设 2026/7/7 16:56:13

【高并发字幕检索系统构建】:掌握Dify缓存周期的4个黄金法则

第一章&#xff1a;高并发字幕检索系统的缓存挑战在构建高并发字幕检索系统时&#xff0c;缓存机制成为性能优化的核心环节。面对每秒数万次的查询请求&#xff0c;传统数据库难以独立支撑实时响应需求&#xff0c;因此引入缓存层以降低延迟、提升吞吐量。然而&#xff0c;字幕…

作者头像 李华
网站建设 2026/7/7 14:51:57

4、Linux 系统中 Shell 的使用指南

Linux 系统中 Shell 的使用指南 1. 认识 Shell Shell 是一个程序,它读取用户输入的命令并运行指定的命令。其名称的由来是它如同海洋中软体动物的坚硬外壳一样,为用户提供了一层保护,使其免受系统外部环境的影响。Shell 是用户与系统之间的中介,所有的交互都通过它进行,…

作者头像 李华
网站建设 2026/7/7 10:37:12

基于Vue的家装平台设计与实现rctt3(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末

系统程序文件列表 系统功能 用户,装饰单位,装修分类,装修报价,我的装修,学装修 开题报告内容 基于 Vue 的家装平台设计与实现开题报告 一、选题背景与意义 选题背景 随着人们生活水平的提高&#xff0c;对居住环境的要求也日益提升&#xff0c;家装行业迎来了蓬勃发展的时…

作者头像 李华
网站建设 2026/7/7 6:49:36

【资深架构师亲授】:Agent集成Dify时不可忽视的参数校验3大雷区

第一章&#xff1a;Agent集成Dify调用参数校验的核心意义在构建智能 Agent 与 Dify 平台深度集成的系统时&#xff0c;调用参数的校验是保障服务稳定性和数据一致性的关键环节。未经校验的输入可能导致异常响应、安全漏洞甚至系统崩溃。因此&#xff0c;在请求发起前对参数进行…

作者头像 李华
网站建设 2026/7/7 10:07:47

【搜索效果翻倍的秘密】:基于Dify的相关性评估模型实战应用

第一章&#xff1a;搜索效果翻倍的核心挑战在现代信息密集的系统中&#xff0c;提升搜索效果并非简单地优化关键词匹配。真正的挑战在于如何在海量数据中实现精准、快速且语义相关的检索结果排序。用户期望搜索不仅能返回字面匹配的内容&#xff0c;还能理解上下文意图&#xf…

作者头像 李华