题目大意
合并k个已经排序好了的链表。
大致思路
可以借助堆排序的思想,使用小顶堆来实现。
先将K个链表的头元素构建成一个最小堆,之后,取堆顶元素,这个结点就是最小的,放入保存结果的集合,接着将原堆顶元素所在链表的下一元素入堆并重新构建最小堆,之后,再取堆顶元素,这个结点就是第二小的……如此往复操作,知道将所有链表的所有元素都取完。
但是,在将原堆顶元素对应的链表中的下一元素入堆时,会遇到一种情况——不存在下一个元素,即原堆顶元素所在链表都取完值了,此时则将最小堆的末尾元素换置换置堆顶,此时真是的最小堆大小将减一。
具体实现如下:
public class MergeKList {
static class Node implements Comparable<Node> {
int val;//元素的值
int indexForLists;//该元素所在的链表处于K个链表的位置
int indexInList;//该元素处在对应链表的位置
public Node(int val, int indexForLists, int indexInList) {
this.val = val;
this.indexForLists = indexForLists;
this.indexInList = indexInList;
}
@Override
public int compareTo(Node o) {
if (val == o.val) return 0;
else if (val > o.val) return 1;
else return -1;
}
}
public static List<Integer> mergeKList(ArrayList<ArrayList<Integer>> lists) {
if (lists == null || lists.size() == 0) return null;
List<Integer> list = new LinkedList<>();
//首先将k个链表的首个元素取出构成最小堆
Node[] minHeap = new Node[lists.size()];
int trueLen = 0;
Iterator<ArrayList<Integer>> iterator = lists.iterator();
while (iterator.hasNext()) {
ArrayList<Integer> arrayList = iterator.next();
if (arrayList != null && arrayList.size() > 0) {
minHeap[trueLen] = new Node(arrayList.get(0), trueLen, 0);
trueLen++;
} else {
//去除无效的链表
iterator.remove();
}
}
for (int i = trueLen / 2 - 1; i >= 0; i--) {
buildMinHeap(minHeap, i, trueLen);
}
while (true) {
//取出最小堆堆堆顶元素
Node heapTop = minHeap[0];
list.add(heapTop.val);
//取原堆顶元素所在链表的下一元素
ArrayList<Integer> arrayList = lists.get(heapTop.indexForLists);
if (heapTop.indexInList < arrayList.size() - 1) {//如果还有下一个元素,则把它对应的Node节点放到堆定
int indexInList = heapTop.indexInList + 1;
minHeap[0] = new Node(arrayList.get(indexInList), heapTop.indexForLists, indexInList);
//重新构建最小堆
buildMinHeap(minHeap, 0, trueLen);
} else {//如果不存在下一个元素,即原堆顶元素所在链表都取完值了,则将最小堆的末尾元素换置换置堆顶,trueLen--
minHeap[0] = minHeap[trueLen - 1];
minHeap[trueLen - 1] = null;
trueLen--;
//如果最小堆真实的大小只剩为1,则表示之剩下一个链表的剩下元素没有合并了,且直接储存结果的集合末尾添加即可
if (trueLen==1) {
heapTop = minHeap[0];
list.add(heapTop.val);
ArrayList<Integer> lastArrayList = lists.get(heapTop.indexForLists);
for (int i = heapTop.indexInList+1; i < lastArrayList.size(); i++) {
list.add(lastArrayList.get(i));
}
break;
} else {
//重新构建最小堆
buildMinHeap(minHeap, 0, trueLen);
}
}
}
return list;
}
public static void buildMinHeap(Node[] nodes, int i, int heapSize) {
int min, left, right;
while (true) {
min = i;
left = 2 * i + 1;
right = left + 1;
if (left < heapSize && nodes[left].compareTo(nodes[min]) < 0) {
min = left;
}
if (right < heapSize && nodes[right].compareTo(nodes[min]) < 0) {
min = right;
}
if (min != i) {
Node temp = nodes[min];
nodes[min] = nodes[i];
nodes[i] = temp;
i = min;
} else {
break;
}
}
}
}
假设总共有k个list,每个list的最大长度是n
维护一个大小为k的堆,每次取堆顶的最小元素放到结果中,然后读取该元素的下一个元素放入堆中,重新维护好。因为每个链表是有序的,每次又是去当前k个元素中最小的,所以当所有链表都读完时结束,这个时候所有元素按从小到大放在结果链表中。这个算法每个元素要读取一次,即是k*n次,然后每次读取元素要把新元素插入堆中要log k的复杂度,所以总时间复杂度是O(nk logk)
。空间复杂度是堆的大小,即为O(k)
。