合并K个升序链表【hard】

40 阅读1分钟
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode[]} lists
 * @return {ListNode}
 */

function ListNode(val, next) {
    this.val = (val === undefined ? 0 : val)
    this.next = (next === undefined ? null : next)
}

var mergeKLists = function (lists) {

    class PriorityQueue {
        constructor(comparator = (a, b) => a < b) { // 默认小顶堆
            this._heap = [];
            this._comparator = comparator;
        }

        _swap(i1, i2) {
            [this._heap[i1], this._heap[i2]] = [this._heap[i2], this._heap[i1]];
        }

        _compare(i1, i2) {
            return this._comparator(this._heap[i1], this._heap[i2]);
        }

        _parent(i) {
            return Math.floor((i - 1) / 2);
        }

        _leftChild(i) {
            return 2 * i + 1;
        }

        _rightChild(i) {
            return 2 * i + 2;
        }

        size() {
            return this._heap.length;
        }

        _shiftUp(i) {
            let parent = this._parent(i);

            if (parent >= 0 && this._compare(i, parent)) {
                this._swap(i, parent);
                this._shiftUp(parent);
            }
        }

        _shiftDown(i) {
            let leftChild = this._leftChild(i);
            let rightChild = this._rightChild(i);

            // if (leftChild < this.size() && !this._compare(i, leftChild)) {
            //     this._swap(i, leftChild);
            //     this._shiftDown(leftChild);
            // }
            // if (rightChild < this.size() && !this._compare(i, rightChild)) {
            //     this._swap(i, rightChild);
            //     this._shiftDown(rightChild);
            // }

            if (leftChild >= this.size()) return;

            let min = leftChild;
            if (rightChild < this.size() && !this._compare(leftChild, rightChild)) {
                min = rightChild;
            }

            if (!this._compare(i, min)) {
                this._swap(i, min);
                this._shiftDown(min);
            }
        }

        push(value) {
            this._heap.push(value);
            this._shiftUp(this._heap.length - 1);
        }

        peek() {
            return this._heap[0];
        }

        pop() {
            if (this.size() == 1) return this._heap.shift();
            const top = this._heap[0];
            this._heap[0] = this._heap[this._heap.length - 1];
            this._heap.pop();
            this._shiftDown(0);
            return top;
        }
    }

    lists = lists.filter(Boolean).filter(v => v.length != 0);
    if (lists.length == 0) return null;
    
    let pq = new PriorityQueue((a, b) => a.val > b.val);
    for (const list of lists) {
        pq.push(list);
    }

    let dummyHead = p = new ListNode(-1);
    while (pq.size()) {
        // console.log(pq._heap);
        let top = pq.pop();
        if (top && top.next) {
            pq.push(top.next);
        }
        p.next = top;
        p = p.next;
    }

    return dummyHead.next;
};

let lists = [[5, 4, 1], [4, 3, 1], [6, 2]];
let newLists = [];
lists.forEach((list) => {
    let head = new ListNode(-1);
    list.reduce((pre, now) => {
        pre.next = new ListNode(now);
        return pre.next;
    }, head);
    newLists.push(head.next);
});

function print(list) {
    return JSON.stringify(list);
}

console.log(print(mergeKLists(newLists)));
  • 利用自己实现的优先级队列,可以实现合并升序数组和降序数组两个类型的题目,只需要修改传入优先级队列的compare函数,和C++中优先级队列的使用方式一样。
  • 另外一个注意点是,在_shiftDown的逻辑中,通过在每个节点选择较小/较大的子节点放到当前节点的位置上,之后再递归的向下调整,会比注释中的,先在左子树中调整,再调整右子树的逻辑,节省很多时间,因为节省掉了很多不必要的递归比较。 image.png
  • btw,一些小trick:通过reduce将数组转换成ListNode;通过JSON.stringify将ListNode打印出来。

Reference

堆排序:原理与实现
拜托,面试别再问我堆(排序)了!