力扣刷题笔记-23 合并k个升序链表
找对象是找绝对盟友
学习了这个算法之后,对于链表更加理解了一些。
链表的理解
链表的数据结构就是数据+指针,数据是int,String等类型,指针是什么?指针就是他自己这种类型的节点,直接指向下一个。
而我们在说,“给定一个链表”,实际上给的是一个头节点。这道题涉及到优先级队列,给定的参数是List
看完这个算法之后,实际上在lists的每个元素里存放的是头节点,最开始下面这段代码是不理解的
for (ListNode head : lists) {
if (head != null){
System.out.println("head.val = " + head.val);
priorityQueue.add(head);
}
}
疑惑的点:
为什么遍历lists,每个元素拿出来,就直接是头节点了呢?
因为链表存放在数组里,本来就是只有头节点。
算法的理解
这个算法主要使用到了优先级队列PriorityQueue这个东西,具体怎么理解后面继续研究,先说在这个算法里优先级队列的原理及作用。
虽说是一个队列,但是并不是遵循先进先出的原则,而是根据元素的优先级,进行排序,当插入一个元素的时候,会根据元素的优先级将其插入到合适的位置,但是删除一个元素的时候,会自动将优先级最高的元素出队。
所以这就引申出了一个问题:什么是优先级?怎么定义优先级?
解答:
实际上,PriorityQueue这个东西,用小顶堆来维护优先级,就是用小顶堆来对元素进行排序,让优先级最高的元素排在数组的最前面(就是最小/最大的元素排在最前面)。小顶堆是方式方法,一种概念,一种实现方式。在priorityQueue.poll()的源码里,有一句是
E result = (E)queue[0]
这说明,我们是将数组的第一个元素拿出来。
当然,拿出来之后还要对生育的元素进行排序
理解这个算法的关键,就是将原本升序排列好的链表都放到优先级队列里边,每次弹出最小的那个元素,将弹出来的元素拼接到新的结果链表上,这样当队列里没有数据的时候,就是合并完成的时候。
@Test
void leetCode23(){
ListNode[] lists = new ListNode[3];
lists[0] = new ListNode(1);
lists[0].next = new ListNode(4);
lists[0].next.next = new ListNode(5);
lists[1] = new ListNode(1);
lists[1].next = new ListNode(3);
lists[1].next.next = new ListNode(4);
lists[2] = new ListNode(2);
lists[2].next = new ListNode(6);
ListNode listNode = mergeKLists(lists);
while (listNode != null){
System.out.print(" " + listNode.val);
listNode = listNode.next;
}
}
private ListNode mergeKLists(ListNode[] lists){
ListNode dummy = new ListNode(-1);
ListNode p = dummy;
PriorityQueue<ListNode> priorityQueue = new PriorityQueue<>(
lists.length, Comparator.comparingInt(a -> a.val)
);
// 将k个链表的头节点存放到优先级队列里边
for (ListNode head : lists) {
if (head != null){
priorityQueue.add(head);
}
}
while (!priorityQueue.isEmpty()){
// 每次弹出最小的元素
ListNode temp = priorityQueue.poll();
// 拼接到结果链表上
p.next = temp;
p = p.next;
// 如果不是尾节点,将后续节点放到队列里面,进入下一次循环。下一次循环的时候仍旧弹出最小的节点
if (temp.next != null){
priorityQueue.add(temp.next);
}
}
return dummy.next;
}