合并 K 个升序链表

题目要求

你需要处理一个链表数组,其中每个链表的元素都是按照升序排列的。你的任务是将这些链表合并成一个新的、同样按照升序排列的单一链表,并返回这个合并后的链表。

解题思路

要解决这个问题,你可以遵循以下步骤:

  1. 初始化:创建一个新的链表头节点,用于存放合并后的链表。同时,维护一个指针,用于指向当前合并链表的最后一个节点。

  2. 比较头节点:比较所有链表的头节点,找出最小的节点。这一步可以通过建立一个最小堆(优先队列)来实现,以便高效地获取当前最小节点。

  3. 链接节点:将找到的最小节点链接到合并链表的末尾,并将该节点所在链表的头指针移动到下一个节点。

  4. 重复步骤:重复步骤 2 和步骤 3,直到所有链表都被完全遍历,即所有链表的头节点都变为 null。

  5. 返回结果:返回初始化时创建的新链表头节点的下一个节点,因为头节点是一个空节点,用于方便操作。

这个问题的关键在于如何高效地找到当前最小的节点。使用最小堆可以在 O(logn)的时间复杂度内完成这一操作(n 是链表的数量)。每次操作后,你需要更新堆,这样才能在下一次比较时获取正确的最小节点。当所有链表都遍历完毕时,你就得到了一个完整的、升序排列的链表。

Golang 版本

package main

import (
	"container/heap"
)

// ListNode is the definition for singly-linked list.
type ListNode struct {
	Val  int
	Next *ListNode
}

// MinHeap is a min-heap of ListNodes.
type MinHeap []*ListNode

func (h MinHeap) Len() int           { return len(h) }
func (h MinHeap) Less(i, j int) bool { return h[i].Val < h[j].Val }
func (h MinHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }

func (h *MinHeap) Push(x interface{}) {
	*h = append(*h, x.(*ListNode))
}

func (h *MinHeap) Pop() interface{} {
	old := *h
	n := len(old)
	x := old[n-1]
	*h = old[0 : n-1]
	return x
}

func mergeKLists(lists []*ListNode) *ListNode {
	minHeap := &MinHeap{}
	heap.Init(minHeap)

	// Add the head of each list to the min heap
	for _, list := range lists {
		if list != nil {
			heap.Push(minHeap, list)
		}
	}

	// Create a dummy head for the result list
	dummy := &ListNode{}
	current := dummy

	// Extract the smallest element from the heap and add it to the result list
	for minHeap.Len() > 0 {
		smallest := heap.Pop(minHeap).(*ListNode)
		current.Next = smallest
		current = current.Next
		if smallest.Next != nil {
			heap.Push(minHeap, smallest.Next)
		}
	}

	return dummy.Next
}

func main() {
	// Example usage:
	// Assume we have an array of linked-list heads `lists`
	// mergedList := mergeKLists(lists)
	// Print or process the merged list as needed
}

这段代码定义了一个最小堆,并使用标准库中的 heap 接口来管理这个堆。mergeKLists 函数首先将每个链表的头节点加入到最小堆中。然后,它不断从堆中取出最小元素,将其加入到结果链表中,并将该元素的下一个节点(如果存在)加入到堆中。这个过程一直持续到堆为空,即所有链表的节点都已经被处理。最后,函数返回合并后的链表的头节点。

C++版本

#include <iostream>
#include <vector>
#include <queue>

// Definition for singly-linked list.
struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
};

struct compare {
    bool operator()(const ListNode* l1, const ListNode* l2) {
        return l1->val > l2->val;
    }
};

ListNode* mergeKLists(std::vector<ListNode*>& lists) {
    std::priority_queue<ListNode*, std::vector<ListNode*>, compare> pq;

    // Initialize the priority queue with the head of each list
    for (ListNode* list : lists) {
        if (list) pq.push(list);
    }

    // Create a new head for the merged list
    ListNode dummy(0);
    ListNode* tail = &dummy;

    // Keep pulling the smallest item from the heap and add it to the merged list
    while (!pq.empty()) {
        tail->next = pq.top();
        pq.pop();
        tail = tail->next;

        if (tail->next) pq.push(tail->next);
    }

    return dummy.next;
}

int main() {
    // Example usage:
    // Assume we have a vector of ListNode pointers `lists`
    // ListNode* mergedList = mergeKLists(lists);
    // Print or process the merged list as needed
}

这段代码使用 C++ 的 priority_queue 来实现最小堆,以维护当前最小节点。mergeKLists 函数首先将每个链表的头节点加入到最小堆中。然后,它不断从堆中取出最小元素,将其加入到结果链表中,并将该元素的下一个节点(如果存在)加入到堆中。这个过程一直持续到堆为空,即所有链表的节点都已经被处理。最后,函数返回合并后的链表的头节点。

Python 版本

from heapq import heappush, heappop

# Definition for singly-linked list.
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

    # The `__lt__` method is necessary for the ListNode to be put in a heap
    def __lt__(self, other):
        return self.val < other.val

def merge_k_lists(lists):
    min_heap = []

    # Initialize the heap
    for head in lists:
        if head:
            heappush(min_heap, head)

    # Create a dummy node to start the merged list
    dummy = ListNode(0)
    current = dummy

    # Extract the smallest node from the heap and add it to the merged list
    while min_heap:
        node = heappop(min_heap)
        current.next = node
        current = current.next
        if node.next:
            heappush(min_heap, node.next)

    return dummy.next

# Example usage:
# lists = [ListNode(1), ListNode(2), ListNode(3)]  # Replace with actual lists
# merged_list = merge_k_lists(lists)
# Print or process the merged list as needed

这段代码首先定义了一个 ListNode 类,用于表示链表节点,并实现了 __lt__ 方法,以便节点可以被放入 Python 的 heapq 模块提供的最小堆中。merge_k_lists 函数使用最小堆来合并链表。它首先将所有链表的头节点放入堆中,然后不断从堆中取出最小节点,并将其添加到合并后的链表中。如果取出的节点后面还有节点,就将下一个节点放入堆中。当堆为空时,所有链表都已合并完毕,函数返回合并后链表的头节点。

Java 版本

import java.util.PriorityQueue;

// Definition for singly-linked list.
public class ListNode {
    int val;
    ListNode next;
    ListNode() {}
    ListNode(int val) { this.val = val; }
    ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}

public class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if (lists == null || lists.length == 0) return null;

        PriorityQueue<ListNode> queue = new PriorityQueue<>(lists.length, (a, b) -> a.val - b.val);

        // Add the first node of each list to the priority queue
        for (ListNode node : lists) {
            if (node != null) {
                queue.add(node);
            }
        }

        ListNode dummy = new ListNode(0);
        ListNode tail = dummy;

        while (!queue.isEmpty()) {
            tail.next = queue.poll();
            tail = tail.next;

            if (tail.next != null) {
                queue.add(tail.next);
            }
        }

        return dummy.next;
    }
}

// Example usage:
// Solution solution = new Solution();
// ListNode[] lists = new ListNode[] { /* initialize your lists here */ };
// ListNode mergedList = solution.mergeKLists(lists);
// Print or process the merged list as needed.

这段代码使用 Java 的 PriorityQueue 类来实现最小堆,以维护当前最小节点。mergeKLists 方法首先检查输入数组是否为空,然后将每个链表的头节点加入到优先队列中。然后,它不断从队列中取出最小元素,将其加入到结果链表中,并将该元素的下一个节点(如果存在)加入到队列中。这个过程一直持续到队列为空,即所有链表的节点都已经被处理。最后,方法返回合并后的链表的头节点。

Rust 版本

#![allow(unused)]
fn main() {
use std::cmp::Ordering;
use std::collections::BinaryHeap;

// Definition for singly-linked list.
#[derive(PartialEq, Eq, Clone)]
pub struct ListNode {
    pub val: i32,
    pub next: Option<Box<ListNode>>,
}

impl ListNode {
    #[inline]
    fn new(val: i32) -> Self {
        ListNode { next: None, val }
    }
}

// To use ListNode in a BinaryHeap, we need to implement Ord and PartialOrd.
impl Ord for ListNode {
    fn cmp(&self, other: &Self) -> Ordering {
        // Flip the ordering here because BinaryHeap is a max heap,
        // but we want to use it as a min heap.
        other.val.cmp(&self.val)
    }
}

impl PartialOrd for ListNode {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

pub fn merge_k_lists(lists: Vec<Option<Box<ListNode>>>) -> Option<Box<ListNode>> {
    let mut heap = BinaryHeap::new();

    // Push the first node of each list into the heap.
    for list in lists {
        if let Some(node) = list {
            heap.push(node);
        }
    }

    // Take the smallest element from the heap and add it to the result list.
    let mut head = Some(Box::new(ListNode::new(0)));
    let mut tail = &mut head;

    while let Some(mut node) = heap.pop() {
        if let Some(next) = node.next.take() {
            heap.push(next);
        }
        tail.as_mut().unwrap().next = Some(node);
        tail = &mut tail.as_mut().unwrap().next;
    }

    head.unwrap().next
}

// Example usage:
// let lists = vec![Some(Box::new(ListNode::new(1))), Some(Box::new(ListNode::new(2))), Some(Box::new(ListNode::new(3)))]; // Replace with actual lists
// let merged_list = merge_k_lists(lists);
// Print or process the merged list as needed.
}

在这个 Rust 版本的代码中,我们首先定义了一个 ListNode 结构体来表示链表节点,并为其实现了 OrdPartialOrd trait,这样我们就可以将 ListNode 存入 BinaryHeap 中。由于 BinaryHeap 是一个最大堆,我们在比较函数中反转了顺序,使其表现得像一个最小堆。

merge_k_lists 函数接受一个 ListNodeVec<Option<Box<ListNode>>>,将每个链表的头节点放入堆中,然后不断从堆中取出最小节点,并将其添加到结果链表中。如果取出的节点后面还有节点,就将下一个节点放入堆中。当堆为空时,所有链表都已合并完毕,函数返回合并后链表的头节点。

总结

上面给出的解法使用了堆(优先队列)来合并多个已排序链表。无论是使用 Python、Java、C++、Rust 还是 Golang,基本思路都是一致的:将每个链表的头节点加入到堆中,然后不断从堆中取出最小节点,将其加入到结果链表中,并将该元素的下一个节点(如果存在)加入到堆中。这个过程一直持续到堆为空,即所有链表的节点都已经被处理。这种方法的时间复杂度为 O(Nlogk),其中 N 是所有链表中元素的总和,k 是链表的个数。