leetcode刷题日记-【23. 合并K个升序链表】

88 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情

题目描述

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

 

示例 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

题目解析

  • k个链表都是升序排列的,将它们合并成一个升序链表
  • 涉及到链表的合并,可以独立成一个小问题,就是两个链表的合并

解题思路

  • 合并两个链表: 需要将当前合并节点之前的节点记录下来,即before;
  • 获取当前两个链表节点值,如果链表k1.val > k2.val ,则将k2.val放入链表中,并将before设置成k2,将k2.next和k1继续进行比较,直到k1,k2链表都为空,最终获取到的链表就是合并之后的链表。 多个链表的合并可以拆分成多对链表的合并,这里采用分治法进行合并。

分治法:把一个复杂的问题分成两个或者更多相同或相似的子问题,再把子问题分成更小的子问题,知道最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。 先将数组中的链表分别两两配对合并成新链表,再将这些新链表合并成新链表。。。。。。依次循环直到最后只剩下一个新链表,就是最终要求的合并链表。

代码实现

/**
 * @param lists
 * @return
 */
public ListNode mergeKLists(ListNode[] lists) {
    ListNode res = new ListNode();
    if (lists == null || lists.length == 0){
        return null;
    }
    // 多个链表的
    return mergeByRange(lists,0,lists.length-1);
}

/**
 * 根据下标进行合并
 *
 * @param lists 需要进行合并的链表数组
 * @param first 第一个元素位置
 * @param second 第二个元素位置
 * @return 返回
 */
private ListNode mergeByRange(ListNode[] lists, int first, int second) {
    // 分治调用
    // 终止条件
    // 如果左右两点相等,直接返回第一个下标元素
    if (first == second){
        return lists[first];
    }
    // 如果下标1大于下标2说明已经超过中点,直接返回空(剩余未配对的元素为空)
    if (first > second){
        return null;
    }
    // 取中位数
    int mid = (first+second)/2;
    return merge(mergeByRange(lists,first,mid),mergeByRange(lists,mid+1,second));
}

/**
 * 合并两个链表
 *
 * @param firstListNode 第一个链表
 * @param secondListNode 第二个链表
 * @return 合并后的新链表
 */
private ListNode merge(ListNode firstListNode, ListNode secondListNode) {
    if (firstListNode == null || secondListNode == null) {
        return firstListNode == null ? secondListNode : firstListNode;
    }
    ListNode res = new ListNode(0);
    // 这里一定要注意 tai的值一直要指向当前操作节点的上一个节点来进行值的比对
    // 当前节点的值是存入next属性中的
    ListNode tail = res;
    while (firstListNode != null && secondListNode != null) {
        if (firstListNode.val > secondListNode.val){
            // 第一次遍历时tail指向的对象地址就是res,所以这里设置的next节点实际上就是在给res设置next节点
            tail.next = secondListNode;
            secondListNode = secondListNode.next;
        }else {
            tail.next = firstListNode;
            firstListNode = firstListNode.next;
        }
        // 第一次循环之后,将tail的对象地址指向为当前存入的链表地址,即res.next地址,依次循环,一直设置的都是res.next.next.next所以res形成了一个新的链表对象
        tail = tail.next;
    }
    // 最后 判断两个节点是否还有值,放入链表尾部。此时的tail实际上指向的是链表的最后一个节点
    tail.next = (firstListNode != null ? firstListNode : secondListNode);
    return res.next;
}