34.合并 K 个升序链表

39 阅读2分钟

题目链接

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

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

解法1 暴力解法

思路

和之前的链表排序一个思路。先保存下来,再对数组进行排序,最后修改指针。

代码

function mergeKLists(lists: Array<ListNode | null>): ListNode | null {
    if (!lists || !lists.length) {
        return null;
    }
    const nodeList = [];
    lists.forEach(list => {
        let cur = list;
        while (cur) {
            nodeList.push(cur);
            cur = cur.next;
        }
    });
    
    nodeList.sort((a, b) => a.val - b.val);

    for (let i = 0; i < nodeList.length; i++) {
        nodeList[i].next = i === nodeList.length - 1 ? null : nodeList[i + 1];
    }

    return nodeList[0] ?? null;
};

时空复杂度

时间复杂度:重点是排序的开销,所以是 O(n logn)

空间复杂度:需要存下所有节点 O(n)

解法2 最小堆解法

思路

暴力解法是把所有节点的值都放数组里排序,再一个个链接指针。但是没有真正利用链表是有序的这个信息。

就好比面前有几堆牌,每堆牌都是有序的,你需要构建一个有序的牌组。

那是不是可以每次就比较堆上最小的数,一个一个组成新的有序牌组。

而这种方式就是最小堆,这个解法最麻烦的地方就是需要自己构造最小堆。

代码

class MinHeap {
    heap: ListNode[];

    constructor() {
        this.heap = [];
    }

    private swap(i: number, j: number) {
        [this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];
    }

    private heapifyUp(index: number) {
        while (index > 0) {
        const parent = Math.floor((index - 1) / 2);
            if (this.heap[parent].val > this.heap[index].val) {
                this.swap(parent, index);
                index = parent;
            } else {
                break;
            }
        }
    }

    private heapifyDown(index: number) {
        const length = this.heap.length;
        while (true) {
            let smallest = index;
            const left = 2 * index + 1;
            const right = 2 * index + 2;

            if (left < length && this.heap[left].val < this.heap[smallest].val) {
                smallest = left;
            }
            if (right < length && this.heap[right].val < this.heap[smallest].val) {
                smallest = right;
            }
            if (smallest !== index) {
                this.swap(index, smallest);
                index = smallest;
            } else {
                break;
            }
        }
    }

    insert(node: ListNode) {
        this.heap.push(node);
        this.heapifyUp(this.heap.length - 1);
    }

    extractMin(): ListNode | null {
        if (this.heap.length === 0) return null;
        const min = this.heap[0];
        const last = this.heap.pop();
        if (this.heap.length > 0 && last) {
            this.heap[0] = last;
            this.heapifyDown(0);
        }
        return min;
    }

    isEmpty(): boolean {
        return this.heap.length === 0;
    }
}

function mergeKLists(lists: Array<ListNode | null>): ListNode | null {
    if (!lists || !lists.length) {
        return null;
    }
    const heap = new MinHeap();
    for (const list of lists) {
        if (list) heap.insert(list);
    }

    const dummy = new ListNode(0);
    let current = dummy;

    while (!heap.isEmpty()) {
        const node = heap.extractMin()!;
        current.next = node;
        current = current.next;
        if (node.next) {
            heap.insert(node.next);
        }
    }

    return dummy.next;
};

时空复杂度

时间复杂度:每个节点入堆一次,出堆一次,O(n logk)k 是数组的长度,n 是总节点个数

空间复杂度:堆的空间每次都只有 k 个节点,所以是 O(k)