Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
一.题目
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 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个升序链表采用双指针是不现实的,不能够满足将所有升序链表进行比对并进行操作,所以我们需要找到一个合适的算法来解决多链表问题。
解决这种多链表合并的问题,需要使用小根堆
的思想,即最开始把所有链表的最小值都放进小根堆
中进行排序摆放,小根堆是利用class
创建的数据结构,每次将小根堆中的栈顶元素弹出加入我们创建的ListNode
链表中,随后在小根堆的底部加入弹出的栈顶元素对应链表的下一个元素,一直循环至小根堆中没有元素的时候就完成了多链表合并。
三、代码:
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
class minHeap{
constructor(){
this.heap = []
}
//交换元素
swap(i,j){
[this.heap[i],this.heap[j]] = [this.heap[j],this.heap[i]]
}
//获取父节点位置
getParentIndex(i){
return (i-1) >> 1 //二进制右移相当于除以2
}
//获取左节点
getLeftIndex(index){
return 2*index + 1
}
//右节点
getRightIndex(index){
return 2*index + 2
}
//插入元素
insert(value){
this.heap.push(value)
//对加入的元素进行上移操作
this.shiftUp(this.size()-1)
}
//判断堆大小
size(){
return this.heap.length
}
//弹出栈顶元素
pop(){
if(this.size() == 1) return this.heap.shift()
let top = this.heap[0]
//把数组最后一位移至栈顶并进行排序
this.heap[0] = this.heap.pop()
this.shiftDown(0)
return top
}
//上移
shiftUp(index){
if(index == 0) return
let parentIndex = this.getParentIndex(index)
if(this.heap[parentIndex] && this.heap[parentIndex].val > this.heap[index].val){
this.swap(parentIndex,index)
//使父节点不断小于它的子节点
this.shiftUp(parentIndex)
}
}
//下移
shiftDown(index){
let leftindex = this.getLeftIndex(index)
let rightindex = this.getRightIndex(index)
if(this.heap[leftindex] && this.heap[index].val > this.heap[leftindex].val){
this.swap(leftindex,index)
this.shiftDown(leftindex)
}
if(this.heap[rightindex] && this.heap[index].val > this.heap[rightindex].val){
this.swap(rightindex,index)
this.shiftDown(rightindex)
}
}
}
/**
* @param {ListNode[]} lists
* @return {ListNode}
*/
var mergeKLists = function(lists) {
if(lists.length == 0) return null
// 利用最小堆算法进行求解
//首先创建一个ListNode数据结构存储每次循环的最小值
let res = new ListNode(-1)
let p = res
//将每个链表的最小值加入最小堆中
let h = new minHeap()
lists.forEach((list,i)=>{
//添加每个链表头到最小堆中
if(list){
h.insert(list)
}
})
//创建循环直到堆内没有元素为止
while(h.size()){
//弹出栈顶元素加入ListNode中
let node = h.pop()
p.next = node
p = p.next
//加入加入该链表后续的节点
if(node.next) h.insert(node.next)
}
return res.next
};
复制代码
四、总结:
对于这种多链表合并问题,我们不能够使用双指针进行求解,因为链表数目不固定,所以使用
小根堆
的思路进行求解。其实除了小根堆的思路,还可以使用多指针
的思路,即每个链表全部遍历一遍,不过这样的复杂度要高一些。