合并k个已排序的链表

图解:(归并排序)

链接

alt

思路:

如果是两个有序链表合并,我们可能会利用归并排序合并阶段的思想:准备双指针分别放在两个链表头,每次取出较小的一个元素加入新的大链表,将其指针后移,继续比较,这样我们出去的都是最小的元素,自然就完成了排序。

其实这道题我们也可以两两比较啊,只要遍历链表数组,取出开头的两个链表,按照上述思路合并,然后新链表再与后一个继续合并,如此循环,知道全部合并完成。但是,这样太浪费时间了。

既然都是归并排序的思想了,那我们可不可以直接归并的分治来做,而不是顺序遍历合并链表呢?答案是可以的!

归并排序是什么?简单来说就是将一个数组每次划分成等长的两部分,对两部分进行排序即是子问题。对子问题继续划分,直到子问题只有1个元素。还原的时候呢,将每个子问题和它相邻的另一个子问题利用上述双指针的方式,1个与1个合并成2个,2个与2个合并成4个,因为这每个单独的子问题合并好的都是有序的,直到合并成原本长度的数组。

对于这k个链表,就相当于上述合并阶段的k个子问题,需要划分为链表数量更少的子问题,直到每一组合并时是两两合并,然后继续往上合并,这个过程基于递归:

终止条件: 划分的时候直到左右区间相等或左边大于右边。 返回值: 每级返回已经合并好的子问题链表。 本级任务: 对半划分,将划分后的子问题合并成新的链表。 具体做法:

step 1:从链表数组的首和尾开始,每次划分从中间开始划分,划分成两半,得到左边n/2n/2n/2个链表和右边n/2n/2n/2个链表。 step 2:继续不断递归划分,直到每部分链表数为1. step 3:将划分好的相邻两部分链表,按照两个有序链表合并的方式合并,合并好的两部分继续往上合并,直到最终合并成一个链表。

代码:

import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    //Merge2()函数进行合并两个区间,传入两个区间表头
    public ListNode Merge2(ListNode pHead1,ListNode pHead2){
        //判断一下,如果一个区间是空,那么就不需要进行合并了,直接返回非空的链表的头结点
        if(pHead1==null){
            return pHead2;
        }
        if(pHead2==null){
            return pHead1;
        }
        //创建一个虚的头结点,设置一个节点cur,取头结点的地址
        ListNode dummynode=new ListNode(0);
        ListNode cur=dummynode;
        //只要两个表为非空,就一直进行判断两种情况 (用归并排序的思想)
        //将两个节点两两比较,把较小的节点连在新的表中,移动对应的头结点为下一个节点
        while(pHead1!=null&&pHead2!=null){
            if(pHead1.val<=pHead2.val){
                cur.next=pHead1;
                pHead1=pHead1.next;
            }else{
                cur.next=pHead2;
                pHead2=pHead2.next;
            }
            //将cur向后移动一个节点
            cur=cur.next;

        }
        //当发现有链表为空的时候,就进行判断,将非空的链表的剩下的节点接到新的链表的后面
        if(pHead1==null){
            cur.next=pHead2;
        }else{
            cur.next=pHead1;
        }
        //返回新形成的链表的头节点:即虚节点的后一个节点
        return dummynode.next;

    }
    //divideMerge函数,传入需要进行分治和合并的序列的区间,传入该区间左右端点
    public ListNode divideMerge(ArrayList<ListNode> lists,int left,int right){
        //如果left>right,那么就表示这个传入的需要进行分治合并的区间的长度为0,那么就直接不用进行分治合并操作,直接返回null
        if(left>right){
            return null;
        }
        //如果发现left==right,就是表示该区间只有一个节点的时候,就表示分治递归(不断划分区间,直到每个区间只有一个节点了),由于对于一个节点,已经是有序的了,就可以直接返回该节点了,用get函数
        else if(left==right){
            return lists.get(left);
        }
        //求出区间的中点:(left+mid)/2
        //递归调用divideMerge函数:就是接着将区间不断划分,直到区间的长度为1,两个区间的端点为[left,mid]和[mid+1,right],注意在划分的过程中如果不能对半划分,就让左区间的长度大于右区间
        //当不断向下递归划分到每个区间的长度为1的时候,就可以合并递归向上两两合并区间了
        int mid=(left+right)/2;
        return Merge2(divideMerge(lists,left,mid),divideMerge(lists,mid+1,right));

    }
   //定义一个函数mergeKLists()用于合并k个序列,传入的参数是用ArrayList存的k个序列
   //调用divideMerge函数进行用归并排序,进行分治和合并,返回形成的新的链表的头节点
   //传入的参数是这k个序列的数组,以及这个数组的左右端点
    public ListNode mergeKLists (ArrayList<ListNode> lists) {
        return divideMerge(lists,0,lists.size()-1);
    }
}