【LeetCode刷题】NO.20---第23题

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

一.题目

23. 合并K个升序链表

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 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链表中,随后在小根堆的底部加入弹出的栈顶元素对应链表的下一个元素,一直循环至小根堆中没有元素的时候就完成了多链表合并。

image.png

三、代码:

/**
 * 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
};
复制代码

四、总结:

对于这种多链表合并问题,我们不能够使用双指针进行求解,因为链表数目不固定,所以使用小根堆的思路进行求解。其实除了小根堆的思路,还可以使用多指针的思路,即每个链表全部遍历一遍,不过这样的复杂度要高一些。

Guess you like

Origin juejin.im/post/7074864897086980104