LeetCode 23.合并 K 个升序链表(分治递归)

LeetCode23|合并 K 个升序链表(分治递归)

一、题目大意

给一个链表数组lists,数组里面每一条链表都已经是升序。把全部链表合并成一条升序链表 ,返回合并后的链表。 输入:lists = [[1,4,5],[1,3,4],[2,6]] 输出:1→1→2→3→4→4→5→6 边界:lists = []lists=[[]],输出空链表。

二、核心思路(分治归并,和 148 思想一致)

  1. :对链表数组的下标区间对半拆分,递归处理左区间、右区间,直到区间只剩 1 条链表直接返回;区间无元素返回null
  2. :左右区间各自递归完成后,得到两条已经有序的链表,复用LeetCode21 合并两个有序链表,将两条链表合并。

时间复杂度:(O(knlog k)),k 链表条数,n 单链表平均节点数 空间复杂度:(O(log k)),递归栈开销

对比朴素循环两两合并:逐条往结果上合并,时间(O(k^2n)),大数据会超时,不推荐面试写。

三、完整 Java 代码

ini 复制代码
/**
 * Definition for singly‑linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if(lists == null || lists.length == 0) {
            return null;
        }
        return merge(lists, 0 , lists.length - 1);
    }

    //递归:合并lists数组 [l, r]下标区间内所有链表
    private ListNode merge(ListNode[] lists, int l, int r) {
        if(l > r) {
            return null;
        }
        if(l == r) {
            return lists[l];
        }
        int mid = (l + r) / 2;
        ListNode leftPart = merge(lists, l, mid);
        ListNode rightPart = merge(lists, mid + 1, r);
        return mergeTwoLists(leftPart, rightPart);
    }

    //LeetCode21:合并两个升序链表
    private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(-1);
        ListNode tail = dummy;
        while(l1 != null && l2 != null) {
            if(l1.val < l2.val) {
                tail.next = l1;
                l1 = l1.next;
            } else {
                tail.next = l2;
                l2 = l2.next;
            }
            tail = tail.next;
        }
        //拼接剩余部分
        if(l1 != null) {
            tail.next = l1;
        } else {
            tail.next = l2;
        }
        return dummy.next;
    }
}

四、实例演示(表格)

输入:lists = [1→4→5 ,1→3→4 ,2→6],下标 0、1、2,调用merge(lists,0,2)

递归调用 l r mid 操作说明 返回值
merge(0,2) 0 2 1 0,12,2 mergeTwoLists (左结果,右结果)
├─merge(0,1) 0 1 0 0,01,1 mergeTwoLists (左结果,右结果)
│ ├─merge(0,0) 0 0 - l==r直接返回 lists 0 1→4→5
│ └─merge(1,1) 1 1 - l==r直接返回 lists 1 1→3→4
│ 合并两条链表 - - - mergeTwoLists 1→1→3→4→4→5
└─merge(2,2) 2 2 - l==r直接返回 lists 2 2→6
顶层合并 - - - mergeTwoLists 1→1→2→3→4→4→5→6

递归树

scss 复制代码
                 merge(0‑2)
                ↙         ↘
        merge(0‑1)        merge(2‑2)
       ↙        ↘             ↓
merge(0‑0)    merge(1‑1)    2→6
    ↓             ↓
1→4→5         1→3→4
       ↘       ↙
  mergeTwoLists合并
       ↓
1→1→3→4→4→5
           ↘         ↙
           mergeTwoLists合并
                 ↓
        1→1→2→3→4→4→5→6

五、语法坑与边界条件

  1. 分清两个函数:

    • merge(lists,l,r):接收数组 + 下标,递归分治;
    • mergeTwoLists(l1,l2):只接收两个链表节点,做两两合并。不要传数组进 mergeTwoLists,会编译报错
  2. if(l > r) return null:拆分后区间不存在,没有链表,返回空;mergeTwoLists可以正常接收null

  3. if(l == r) return lists[l]:区间只剩 1 条链表,直接返回,不再拆分。

  4. mergeTwoLists 内部,tail = tail.next不能漏写。

  5. 边界处理:数组为空lists.length==0直接返回 null;数组里面存放空链表null也可以被 mergeTwoLists 正常处理。

六、个人感悟

  1. 本题是21 题的升级版,分治思想和 148 排序链表自顶向下归并一模一样,旧代码复用。
  2. 朴素循环两两合并写法简单,但是时间效率差,面试优先写分治版本。
  3. 递归终止条件两个:l>r(无元素)、l==r(单个元素),两个缺一不可。
  4. 还有小根堆(优先队列)解法,也是(O(knlog k)),适合了解思路。
相关推荐
圣保罗的大教堂1 小时前
leetcode 836. 矩形重叠 简单
leetcode
土司大王1 小时前
LeetCode hot100——394.字符串解码:Java 双栈模拟
java·算法·leetcode
a187927218311 小时前
【算法】双指针与滑动窗口(三):相向双指针——比较、排除、收缩
算法·leetcode·双指针·滑动窗口·原理·相向双指针·算法讲解
6Hzlia2 小时前
【Classic 150 刷题计划】 LeetCode 14. 最长公共前缀 | C++ 纵向扫描法与防越界细节
c++·算法·leetcode
Tisfy18 小时前
LeetCode 0836.矩形重叠:xy两方向分别看
数学·leetcode·题解·模拟
青山木19 小时前
Hot 100 --- 最长递增子序列
java·数据结构·算法·leetcode·动态规划
土司大王20 小时前
LeetCode hot100——33.搜索旋转排序数组:Java 二分模板与 O(log n) 实现
数据结构·算法·leetcode
辰辉创聚20 小时前
禽流感病毒分子基础:表面抗原与内部转录复合体的功能解析
ide·leetcode·rabbitmq·重组血凝素蛋白·甲型流感病毒核蛋白·禽流感单抗
ysu_03141 天前
08-二叉树遍历:四种方式详解
c语言·数据结构·算法·leetcode
土司大王1 天前
LeetCode hot100——153.寻找旋转排序数组中的最小值:Java 二分模板与 O(log n) 分析
java·算法·leetcode