题目
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 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
思路
这道题使用的方法是优先级队列,优先级队列是一种当插入元素时,可以帮助你自动排好序的数据结构。而底层的原理就是二叉堆。
二叉堆是一颗完全二叉树,但是这个树是存储在数组之中。
举个例子,父亲节点的下标如果是a,那么左子节点下标就是2a,右子节点就是2a+1。也就是孩子结点的坐标可以利用父节点的索引值找到,下面是一张借用labuladong博主的图片:
并且二叉堆还分为最大堆和最小堆,最大堆的意思就是父亲节点的值永远大于孩子节点。最小堆相反。
优先级队列中有四个关键方法:上浮,下沉,插入,删除。
上浮:
public class MaxPQ <Key extends Comparable<Key>> {
private void swim(int x) {
// 如果浮到堆顶,就不能再上浮了
while (x > 1 && less(parent(x), x)) {
// 如果第 x 个元素比上层大
// 将 x 换上去
swap(parent(x), x);
//我们始终要知道,这是数组。
//假如一开始只有两个元素,x是2,经过交换之后,下标1,2的元素已经互换了位置,x所代表的元素始终是原先的第二个元素,所以我们需要更新X的值
x = parent(x);
}
}
}
下沉
与上浮同理:只是在上浮的基础上补了一个先比较左右节点,再去和父节点进行比较。
public class MaxPQ <Key extends Comparable<Key>> {
private void sink(int x) {
// 如果沉到堆底,就沉不下去了
while (left(x) <= size) {
// 先假设左边节点较大
int max = left(x);
// 如果右边节点存在,比一下大小
if (right(x) <= size && less(max, right(x)))
max = right(x);
// 结点 x 比俩孩子都大,就不必下沉了
if (less(max, x)) break;
// 否则,不符合最大堆的结构,下沉 x 结点
swap(x, max);
x = max;
}
}
}
insert:
public class MaxPQ <Key extends Comparable<Key>> {
// 为了节约篇幅,省略上文给出的代码部分...
public void insert(Key e) {
size++;
// 先把新元素加到最后
pq[size] = e;
// 然后让它上浮到正确的位置
swim(size);
}
}
delMax:
public class MaxPQ <Key extends Comparable<Key>> {
// 为了节约篇幅,省略上文给出的代码部分...
public Key delMax() {
// 最大堆的堆顶就是最大元素
Key max = pq[1];
// 把这个最大元素换到最后,删除之
swap(1, size);
pq[size] = null;
size--;
// 让 pq[1] 下沉到正确位置
sink(1);
return max;
}
}
以上代码和思路取自于labuladong的博客 :至于内容方面都来自于本人二叉堆详解实现优先级队列 | labuladong 的算法笔记
关于这道题的思路
首先我们将k个链表的头节点放入我们的优先级队列中。由于是升序排列,所以我们使用小顶堆进行排序。
之后我们获取这三个节点之中值最小的一个节点,并把他放入我们构建的新链表之中,并之后,链表节点顺势后移一位,方便继续添加。
//poll函数可以获取最高优先级的节点
ListNode node = pq.poll();
p.next = node;
p = p.next;
我们的链表移动好了之后,接下来我们要移动优先级队列中的节点。现在node所指向的不是最高优先级的节点了,我们首先需要让node=node.next,然后再将node添加进我们的队列之中,由于优先级队列会自动排序,并且队列是先进先出的数据结构,所以我们直接添加就可以进行排序,最高优先级的节点一定在第一位。但是在我们node添加的时候需要保证,node.next不为空。
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if(lists.length==0){
return null;
}
ListNode dummy = new ListNode(-1);
ListNode p = dummy;
//选把链表整合到一起,然后利用优先级队列,小顶堆进行排序。
PriorityQueue<ListNode> pq = new PriorityQueue<>(
lists.length, (a, b)->(a.val - b.val));
for(ListNode head:lists){
if(head!=null)
pq.add(head);
}
while(!pq.isEmpty()){
//这里获取的是三个头节点之中最小的节点,也是所有节点之中最小的
ListNode node = pq.poll();
p.next = node;
p = p.next;
if(node.next!=null){
//保证node的链表更新,node每次不是同一个链表的值,还得保证链表进入到优先级队列制作,所以不可以直接node=node.next
pq.add(node.next);
//因为这是一个队列,所以add方法会自动弹出第一个,然后将node.next放进去
}
}
return dummy.next;
}
}