
一、面试问题
给定 k 个长度各不相同的有序链表 ,需要将它们合并为单个有序链表,并保持原有升序顺序。
示例 1:
输入:

输出:

解释:合并后的链表保持升序排列,每个元素都大于前一个元素。
示例 2:
输入:

输出:

二、[朴素解法-1] 依次逐个合并链表 ------ 时间复杂度 :O (N × k2)),空间复杂度:O(1)
(一) 解法思路
思路:先初始化结果链表为空。然后利用合并两个有序链表的思想,将每条链表逐一合并到结果链表中。始终把结果链表当作第一个链表,把待合并的链表当作第二个链表,最终返回合并后的结果链表。
(二) 使用 6 种语言实现
1. C++
cpp
#include <iostream>
#include<vector>
using namespace std;
class Node {
public:
int data;
Node* next;
Node(int x) {
data = x;
next = nullptr;
}
};
// 合并两个链表的函数
Node* mergeTwo(Node* head1, Node* head2) {
// 创建一个哑节点以简化合并过程
Node* dummy = new Node(-1);
Node* curr = dummy;
// 遍历两个链表
while (head1 != nullptr && head2 != nullptr) {
// 将较小的节点加入合并后的链表
if (head1->data <= head2->data) {
curr->next = head1;
head1 = head1->next;
} else {
curr->next = head2;
head2 = head2->next;
}
curr = curr->next;
}
// 如果还有剩余链表,直接拼接在合并链表的末尾
if (head1 != nullptr) {
curr->next = head1;
} else {
curr->next = head2;
}
// 返回从哑节点的下一个节点开始的合并链表
return dummy->next;
}
// 合并 K 个有序链表的函数
Node* mergeKLists(vector<Node*>& arr) {
// 初始化结果为空
Node *res = nullptr;
// 将所有链表依次与 res 合并,并持续更新 res
for (Node *node : arr)
res = mergeTwo(res, node);
return res;
}
void printList(Node* node) {
while (node != nullptr) {
cout << node->data;
if(node->next)
cout<<" -> ";
node = node->next;
}
}
int main() {
int k = 3;
vector<Node*> arr(k);
arr[0] = new Node(1);
arr[0]->next = new Node(3);
arr[0]->next->next = new Node(5);
arr[0]->next->next->next = new Node(7);
arr[1] = new Node(2);
arr[1]->next = new Node(4);
arr[1]->next->next = new Node(6);
arr[1]->next->next->next = new Node(8);
arr[2] = new Node(0);
arr[2]->next = new Node(9);
arr[2]->next->next = new Node(10);
arr[2]->next->next->next = new Node(11);
Node* head = mergeKLists(arr);
printList(head);
return 0;
}
2. C
cpp
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node* next;
};
struct Node* createNode(int data);
// 合并两个链表的函数
struct Node* mergeTwo(struct Node* head1, struct Node* head2) {
// 创建一个哑节点以简化合并过程
struct Node* dummy = createNode(-1);
struct Node* curr = dummy;
// 遍历两个链表
while (head1 != NULL && head2 != NULL) {
// 将较小的节点加入合并后的链表
if (head1->data <= head2->data) {
curr->next = head1;
head1 = head1->next;
} else {
curr->next = head2;
head2 = head2->next;
}
curr = curr->next;
}
// 如果还有剩余链表,直接拼接在合并链表的末尾
if (head1 != NULL) {
curr->next = head1;
} else {
curr->next = head2;
}
// 返回从哑节点的下一个节点开始的合并链表
struct Node* merged = dummy->next;
free(dummy);
return merged;
}
// 合并 K 个有序链表的函数
struct Node* mergeKLists(struct Node** arr, int k) {
// 初始化结果为空
struct Node* res = NULL;
// 将所有链表依次与 res 合并,并持续更新 res
for (int i = 0; i < k; i++)
res = mergeTwo(res, arr[i]);
return res;
}
void printList(struct Node* node) {
while (node != NULL) {
printf("%d", node->data);
if(node->next)
printf(" -> ");
node = node->next;
}
}
struct Node* createNode(int data) {
struct Node* newNode =
(struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
int main() {
int k = 3;
struct Node* arr[k];
arr[0] = createNode(1);
arr[0]->next = createNode(3);
arr[0]->next->next = createNode(5);
arr[0]->next->next->next = createNode(7);
arr[1] = createNode(2);
arr[1]->next = createNode(4);
arr[1]->next->next = createNode(6);
arr[1]->next->next->next = createNode(8);
arr[2] = createNode(0);
arr[2]->next = createNode(9);
arr[2]->next->next = createNode(10);
arr[2]->next->next->next = createNode(11);
struct Node* head = mergeKLists(arr, k);
printList(head);
return 0;
}
3. Java
java
import java.util.List;
import java.util.ArrayList;
class Node {
int data;
Node next;
Node(int x) {
data = x;
next = null;
}
}
class DSA {
// 合并两个链表的函数
static Node mergeTwo(Node head1, Node head2) {
// 创建一个哑节点以简化合并过程
Node dummy = new Node(-1);
Node curr = dummy;
// 遍历两个链表
while (head1 != null && head2 != null) {
// 将较小的节点加入合并后的链表
if (head1.data <= head2.data) {
curr.next = head1;
head1 = head1.next;
} else {
curr.next = head2;
head2 = head2.next;
}
curr = curr.next;
}
// 如果还有剩余链表,直接拼接在合并链表的末尾
if (head1 != null) {
curr.next = head1;
} else {
curr.next = head2;
}
// 返回从哑节点的下一个节点开始的合并链表
return dummy.next;
}
// 合并 K 个有序链表的函数
static Node mergeKLists(List<Node> arr) {
// 初始化结果为空
Node res = null;
// 将所有链表依次与 res 合并,并持续更新 res
for (Node node : arr)
res = mergeTwo(res, node);
return res;
}
static void printList(Node node) {
while (node != null) {
System.out.print(node.data);
if(node.next!=null)
System.out.print(" -> ");
node = node.next;
}
}
public static void main(String[] args) {
List<Node> arr = new ArrayList<>();
arr.add(new Node(1));
arr.get(0).next = new Node(3);
arr.get(0).next.next = new Node(5);
arr.get(0).next.next.next = new Node(7);
arr.add(new Node(2));
arr.get(1).next = new Node(4);
arr.get(1).next.next = new Node(6);
arr.get(1).next.next.next = new Node(8);
arr.add(new Node(0));
arr.get(2).next = new Node(9);
arr.get(2).next.next = new Node(10);
arr.get(2).next.next.next = new Node(11);
Node head = mergeKLists(arr);
printList(head);
}
}
4. Python
python
class Node:
def __init__(self, x):
self.data = x
self.next = None
# 合并两个链表的函数
def mergeTwo(head1, head2):
# 创建一个哑节点以简化合并过程
dummy = Node(-1)
curr = dummy
# 遍历两个链表
while head1 is not None and head2 is not None:
# 将较小的节点加入合并后的链表
if head1.data <= head2.data:
curr.next = head1
head1 = head1.next
else:
curr.next = head2
head2 = head2.next
curr = curr.next
# 如果还有剩余链表,直接拼接在合并链表的末尾
if head1 is not None:
curr.next = head1
else:
curr.next = head2
# 返回从哑节点的下一个节点开始的合并链表
return dummy.next
# 合并 K 个有序链表的函数
def mergeKLists(arr):
# 初始化结果为空
res = None
# 将所有链表依次与 res 合并,并持续更新 res
for node in arr:
res = mergeTwo(res, node)
return res
def printList(node):
while node is not None:
print(f"{node.data}", end="")
if node.next is not None:
print(" -> ", end="")
node = node.next
print()
if __name__ == "__main__":
arr = []
node1 = Node(1)
node1.next = Node(3)
node1.next.next = Node(5)
node1.next.next.next = Node(7)
arr.append(node1)
node2 = Node(2)
node2.next = Node(4)
node2.next.next = Node(6)
node2.next.next.next = Node(8)
arr.append(node2)
node3 = Node(0)
node3.next = Node(9)
node3.next.next = Node(10)
node3.next.next.next = Node(11)
arr.append(node3)
head = mergeKLists(arr)
printList(head)
5. C#
cs
using System;
using System.Collections.Generic;
class Node {
public int data;
public Node next;
public Node(int x) {
data = x;
next = null;
}
}
class DSA {
static Node mergeTwo(Node head1, Node head2) {
// 创建一个哑节点以简化合并过程
Node dummy = new Node(-1);
Node curr = dummy;
// 遍历两个链表
while (head1 != null && head2 != null) {
// 将较小的节点加入合并后的链表
if (head1.data <= head2.data) {
curr.next = head1;
head1 = head1.next;
} else {
curr.next = head2;
head2 = head2.next;
}
curr = curr.next;
}
// 如果还有剩余链表,直接拼接在合并链表的末尾
if (head1 != null) {
curr.next = head1;
} else {
curr.next = head2;
}
// 返回从哑节点的下一个节点开始的合并链表
return dummy.next;
}
static Node mergeKLists(List<Node> arr) {
// 初始化结果为空
Node res = null;
// 将所有链表依次与 res 合并,并持续更新 res
foreach (Node node in arr)
res = mergeTwo(res, node);
return res;
}
static void printList(Node head) {
Node temp = head;
while (temp != null) {
Console.Write(temp.data + "");
if(temp.next!=null)
Console.Write(" -> ");
temp = temp.next;
}
Console.WriteLine();
}
static void Main(string[] args) {
List<Node> arr = new List<Node>();
Node node1 = new Node(1);
node1.next = new Node(3);
node1.next.next = new Node(5);
node1.next.next.next = new Node(7);
arr.Add(node1);
Node node2 = new Node(2);
node2.next = new Node(4);
node2.next.next = new Node(6);
node2.next.next.next = new Node(8);
arr.Add(node2);
Node node3 = new Node(0);
node3.next = new Node(9);
node3.next.next = new Node(10);
node3.next.next.next = new Node(11);
arr.Add(node3);
Node head = mergeKLists(arr);
printList(head);
}
}
6. JavaScript
javascript
class Node {
constructor(x) {
this.data = x;
this.next = null;
}
}
// 合并两个链表的函数
function mergeTwo(head1, head2) {
// 创建一个哑节点以简化合并过程
let dummy = new Node(-1);
let curr = dummy;
// 遍历两个链表
while (head1 !== null && head2 !== null) {
// 将较小的节点加入合并后的链表
if (head1.data <= head2.data) {
curr.next = head1;
head1 = head1.next;
} else {
curr.next = head2;
head2 = head2.next;
}
curr = curr.next;
}
// 如果还有剩余链表,直接拼接在合并链表的末尾
if (head1 !== null) {
curr.next = head1;
} else {
curr.next = head2;
}
// 返回从哑节点的下一个节点开始的合并链表
return dummy.next;
}
// 合并 K 个有序链表的函数
function mergeKLists(arr) {
// 初始化结果为空
let res = null;
// 将所有链表依次与 res 合并,并持续更新 res
for (let node of arr)
res = mergeTwo(res, node);
return res;
}
function printList(node) {
while (node !== null) {
console.log(node.data + " ");
node = node.next;
}
}
// 测试代码
let arr = [];
let node1 = new Node(1);
node1.next = new Node(3);
node1.next.next = new Node(5);
node1.next.next.next = new Node(7);
arr.push(node1);
let node2 = new Node(2);
node2.next = new Node(4);
node2.next.next = new Node(6);
node2.next.next.next = new Node(8);
arr.push(node2);
let node3 = new Node(0);
node3.next = new Node(9);
node3.next.next = new Node(10);
node3.next.next.next = new Node(11);
arr.push(node3);
let head = mergeKLists(arr);
printList(head);
(三) 代码输出和算法复杂度
输出:
0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11
时间复杂度:O (n×k²)。为简化分析,我们假设每条链表的长度都为 n。在最坏情况下,总耗时为 n + 2n + 3n + ... + k×n。该数列的和为 n×k×(k+1)/2,时间复杂度为 O (n×k²)。
空间复杂度:O(1)。
三、【朴素解法-2】重复选取最小节点 ------ 时间复杂度 O(n * k2),空间复杂度 O (1)
(一) 解法思路
该方法的思路是:遍历全部 k 个链表的头节点,将值最小的头节点添加到结果链表中,然后将该链表的头指针后移一位。重复此过程,直到所有节点都被处理完毕。
分步实现思路:
- 为结果链表初始化一个哑节点头。
- 在全部 k 个链表中找到值最小的节点。
- 将找到最小节点的那个链表的当前指针后移一位。
- 把这个值最小的节点添加到结果链表的末尾。
- 重复上述步骤,直到所有节点都被处理完毕。
(二) 使用 5 种语言实现
1. C++
cpp
#include <iostream>
#include<vector>
using namespace std;
class Node {
public:
int data;
Node* next;
Node(int x) {
data = x;
next = nullptr;
}
};
// 获取值最小的节点的函数
Node* getMinNode(vector<Node*> &arr) {
Node* mini = nullptr;
int index = -1;
for (int i=0; i<arr.size(); i++) {
// 如果当前链表已处理完毕
if (arr[i]==nullptr) continue;
// 如果最小节点未设置,或当前头节点值更小
if (mini==nullptr || arr[i]->data<mini->data) {
index = i;
mini = arr[i];
}
}
// 将对应链表的头节点后移一位
if (index!=-1) arr[index] = arr[index]->next;
return mini;
}
// 合并 K 个有序链表的函数
Node* mergeKLists(vector<Node*>& arr) {
// 创建一个哑节点以简化合并过程
Node* dummy = new Node(-1);
Node* tail = dummy;
Node* mini = getMinNode(arr);
// 处理所有节点
while (mini != nullptr) {
// 将最小节点添加到结果链表
tail->next = mini;
tail = mini;
// 查找下一个最小节点
mini = getMinNode(arr);
}
// 返回从哑节点的下一个节点开始的合并链表
return dummy->next;
}
void printList(Node* node) {
while (node != nullptr) {
cout << node->data;
if(node->next)
cout<<" -> ";
node = node->next;
}
}
int main() {
int k = 3;
vector<Node*> arr(k);
arr[0] = new Node(1);
arr[0]->next = new Node(3);
arr[0]->next->next = new Node(5);
arr[0]->next->next->next = new Node(7);
arr[1] = new Node(2);
arr[1]->next = new Node(4);
arr[1]->next->next = new Node(6);
arr[1]->next->next->next = new Node(8);
arr[2] = new Node(0);
arr[2]->next = new Node(9);
arr[2]->next->next = new Node(10);
arr[2]->next->next->next = new Node(11);
Node* head = mergeKLists(arr);
printList(head);
return 0;
}
2. Java
java
import java.util.List;
import java.util.ArrayList;
class Node {
int data;
Node next;
Node(int x) {
data = x;
next = null;
}
}
class DSA {
// 获取值最小的节点的函数
static Node getMinNode(List<Node> arr) {
Node mini = null;
int index = -1;
for (int i = 0; i < arr.size(); i++) {
// 如果当前链表已处理完毕
if (arr.get(i) == null) continue;
// 如果最小节点未设置,或当前头节点值更小
if (mini == null || arr.get(i).data < mini.data) {
index = i;
mini = arr.get(i);
}
}
// 将对应链表的头节点后移一位
if (index != -1) arr.set(index, arr.get(index).next);
return mini;
}
// 合并 K 个有序链表的函数
static Node mergeKLists(List<Node> arr) {
// 创建一个哑节点以简化合并过程
Node dummy = new Node(-1);
Node tail = dummy;
Node mini = getMinNode(arr);
// 处理所有节点
while (mini != null) {
// 将最小节点添加到结果链表
tail.next = mini;
tail = mini;
// 查找下一个最小节点
mini = getMinNode(arr);
}
// 返回从哑节点的下一个节点开始的合并链表
return dummy.next;
}
static void printList(Node node) {
while (node != null) {
System.out.print(node.data);
if(node.next!=null)
{
System.out.print(" -> ");
}
node = node.next;
}
}
public static void main(String[] args) {
List<Node> arr = new ArrayList<>();
arr.add(new Node(1));
arr.get(0).next = new Node(3);
arr.get(0).next.next = new Node(5);
arr.get(0).next.next.next = new Node(7);
arr.add(new Node(2));
arr.get(1).next = new Node(4);
arr.get(1).next.next = new Node(6);
arr.get(1).next.next.next = new Node(8);
arr.add(new Node(0));
arr.get(2).next = new Node(9);
arr.get(2).next.next = new Node(10);
arr.get(2).next.next.next = new Node(11);
Node head = mergeKLists(arr);
printList(head);
}
}
3. Python
python
class Node:
def __init__(self, x):
self.data = x
self.next = None
# 获取值最小的节点的函数
def getMinNode(arr):
mini = None
index = -1
for i in range(len(arr)):
# 如果当前链表已处理完毕
if arr[i] is None:
continue
# 如果最小节点未设置,或当前头节点值更小
if mini is None or arr[i].data < mini.data:
index = i
mini = arr[i]
# 将对应链表的头节点后移一位
if index != -1:
arr[index] = arr[index].next
return mini
# 合并 K 个有序链表的函数
def mergeKLists(arr):
# 创建一个哑节点以简化合并过程
dummy = Node(-1)
tail = dummy
mini = getMinNode(arr)
# 处理所有节点
while mini:
# 将最小节点添加到结果链表
tail.next = mini
tail = mini
# 查找下一个最小节点
mini = getMinNode(arr)
# 返回从哑节点的下一个节点开始的合并链表
return dummy.next
def printList(node):
while node is not None:
print(f"{node.data}", end="")
if node.next is not None:
print(" -> ", end="")
node = node.next
print()
if __name__ == "__main__":
arr = [None] * 3
arr[0] = Node(1)
arr[0].next = Node(3)
arr[0].next.next = Node(5)
arr[0].next.next.next = Node(7)
arr[1] = Node(2)
arr[1].next = Node(4)
arr[1].next.next = Node(6)
arr[1].next.next.next = Node(8)
arr[2] = Node(0)
arr[2].next = Node(9)
arr[2].next.next = Node(10)
arr[2].next.next.next = Node(11)
head = mergeKLists(arr)
printList(head)
4. C#
cs
using System;
using System.Collections.Generic;
class Node {
public int data;
public Node next;
public Node(int x) {
data = x;
next = null;
}
}
class DSA {
// 获取值最小的节点的函数
static Node getMinNode(List<Node> arr) {
Node mini = null;
int index = -1;
for (int i = 0; i < arr.Count; i++) {
// 如果当前链表已处理完毕
if (arr[i] == null) continue;
// 如果最小节点未设置,或当前头节点值更小
if (mini == null || arr[i].data < mini.data) {
index = i;
mini = arr[i];
}
}
// 将对应链表的头节点后移一位
if (index != -1) arr[index] = arr[index].next;
return mini;
}
// 合并 K 个有序链表的函数
static Node mergeKLists(List<Node> arr) {
// 创建一个哑节点以简化合并过程
Node dummy = new Node(-1);
Node tail = dummy;
Node mini = getMinNode(arr);
// 处理所有节点
while (mini != null) {
// 将最小节点添加到结果链表
tail.next = mini;
tail = mini;
// 查找下一个最小节点
mini = getMinNode(arr);
}
// 返回从哑节点的下一个节点开始的合并链表
return dummy.next;
}
static void printList(Node head) {
Node temp = head;
while (temp != null) {
Console.Write(temp.data + "");
if(temp.next!=null)
Console.Write(" -> ");
temp = temp.next;
}
Console.WriteLine();
}
static void Main() {
List<Node> arr = new List<Node>();
arr.Add(new Node(1));
arr[0].next = new Node(3);
arr[0].next.next = new Node(5);
arr[0].next.next.next = new Node(7);
arr.Add(new Node(2));
arr[1].next = new Node(4);
arr[1].next.next = new Node(6);
arr[1].next.next.next = new Node(8);
arr.Add(new Node(0));
arr[2].next = new Node(9);
arr[2].next.next = new Node(10);
arr[2].next.next.next = new Node(11);
Node head = mergeKLists(arr);
printList(head);
}
}
5. JavaScript
javascript
class Node {
constructor(x) {
this.data = x;
this.next = null;
}
}
// 获取值最小的节点的函数
function getMinNode(arr) {
let mini = null;
let index = -1;
for (let i = 0; i < arr.length; i++) {
// 如果当前链表已处理完毕
if (arr[i] === null) continue;
// 如果最小节点未设置,或当前头节点值更小
if (mini === null || arr[i].data < mini.data) {
index = i;
mini = arr[i];
}
}
// 将对应链表的头节点后移一位
if (index !== -1) arr[index] = arr[index].next;
return mini;
}
// 合并 K 个有序链表的函数
function mergeKLists(arr) {
// 创建一个哑节点以简化合并过程
let dummy = new Node(-1);
let tail = dummy;
let mini = getMinNode(arr);
// 处理所有节点
while (mini !== null) {
// 将最小节点添加到结果链表
tail.next = mini;
tail = mini;
// 查找下一个最小节点
mini = getMinNode(arr);
}
// 返回从哑节点的下一个节点开始的合并链表
return dummy.next;
}
function printList(node) {
while (node !== null) {
process.stdout.write(node.data.toString());
if (node.next !== null) {
process.stdout.write(" -> ");
}
node = node.next;
}
}
let arr = [];
arr.push(new Node(1));
arr[0].next = new Node(3);
arr[0].next.next = new Node(5);
arr[0].next.next.next = new Node(7);
arr.push(new Node(2));
arr[1].next = new Node(4);
arr[1].next.next = new Node(6);
arr[1].next.next.next = new Node(8);
arr.push(new Node(0));
arr[2].next = new Node(9);
arr[2].next.next = new Node(10);
arr[2].next.next.next = new Node(11);
let head = mergeKLists(arr);
printList(head);
(三) 代码输出和算法复杂度
输出:
0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11
时间复杂度:O (n×k²)。总共有 n×k 个节点(假设每条链表有 O (n) 个节点),而找到最小节点需要遍历 k 次,因此处理所有 n×k 个节点总耗时为 n×k×k。
空间复杂度:O(1)。
四、【优化解法-1】使用最小堆 ------ 时间复杂度 O (n²),空间复杂度 O (1)
(一) 解法思路
该解法主要是对前一种方法的优化。我们不再通过线性遍历数组 来查找最小值,而是使用最小堆 数据结构,将这一步操作的时间复杂度降低到 O(log k)。
(二) 使用 5 种语言实现
1. C++
cpp
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
class Node {
public:
int data;
Node* next;
Node(int x) {
data = x;
next = nullptr;
}
};
// 用于最小堆的自定义比较器
class Compare {
public:
bool operator()(Node* a, Node* b) {
return a->data > b->data;
}
};
// 合并 K 个有序链表的函数
Node* mergeKLists(vector<Node*>& arr) {
// 定义最小堆
priority_queue<Node*, vector<Node*>, Compare> pq;
// 将 k 个链表的头节点插入堆中
for (Node* head: arr) {
if (head != nullptr) pq.push(head);
}
// 初始化哑节点头
Node* dummy = new Node(-1);
Node* tail = dummy;
while (!pq.empty()) {
// 取出堆顶的最小节点
Node* top = pq.top();
pq.pop();
// 将该节点添加到结果链表
tail->next = top;
tail = top;
// 如果该节点存在后继节点,则将其加入堆
if (top->next!=nullptr) {
pq.push(top->next);
}
}
// 返回合并后的链表
return dummy->next;
}
void printList(Node* node) {
while (node != nullptr) {
cout << node->data;
if(node->next) cout << "->";
node = node->next;
}
}
int main() {
int k = 3;
vector<Node*> arr(k);
arr[0] = new Node(1);
arr[0]->next = new Node(3);
arr[0]->next->next = new Node(5);
arr[0]->next->next->next = new Node(7);
arr[1] = new Node(2);
arr[1]->next = new Node(4);
arr[1]->next->next = new Node(6);
arr[1]->next->next->next = new Node(8);
arr[2] = new Node(0);
arr[2]->next = new Node(9);
arr[2]->next->next = new Node(10);
arr[2]->next->next->next = new Node(11);
Node* head = mergeKLists(arr);
printList(head);
return 0;
}
2. Java
java
import java.util.List;
import java.util.ArrayList;
import java.util.PriorityQueue;
class Node {
int data;
Node next;
Node(int x) {
data = x;
next = null;
}
}
class DSA {
// 合并 K 个有序链表的函数
static Node mergeKLists(List<Node> arr) {
// 创建最小堆(按节点值升序排列)
PriorityQueue<Node> pq = new PriorityQueue<>((a, b) -> a.data - b.data);
// 将 k 个链表的头节点插入堆中
for (Node head : arr) {
if (head != null) pq.add(head);
}
// 初始化哑节点头
Node dummy = new Node(-1);
Node tail = dummy;
while (!pq.isEmpty()) {
// 取出堆顶的最小节点
Node top = pq.poll();
// 将该节点添加到结果链表
tail.next = top;
tail = top;
// 如果该节点存在后继节点,则将其加入堆
if (top.next != null) {
pq.add(top.next);
}
}
// 返回合并后的链表
return dummy.next;
}
static void printList(Node node) {
while (node != null) {
System.out.print(node.data);
if(node.next != null){
System.out.print("->");
}
node = node.next;
}
}
public static void main(String[] args) {
int k = 3;
List<Node> arr = new ArrayList<>();
arr.add(new Node(1));
arr.get(0).next = new Node(3);
arr.get(0).next.next = new Node(5);
arr.get(0).next.next.next = new Node(7);
arr.add(new Node(2));
arr.get(1).next = new Node(4);
arr.get(1).next.next = new Node(6);
arr.get(1).next.next.next = new Node(8);
arr.add(new Node(0));
arr.get(2).next = new Node(9);
arr.get(2).next.next = new Node(10);
arr.get(2).next.next.next = new Node(11);
Node head = mergeKLists(arr);
printList(head);
}
}
3. Python
python
import heapq
class Node:
def __init__(self, x):
self.data = x
self.next = None
# 合并 K 个有序链表的函数
def mergeKLists(arr):
pq = []
# 将 k 个链表的头节点插入最小堆
for i in range(0, len(arr)):
head = arr[i]
if head is not None:
heapq.heappush(pq, (head.data, i, head))
# 初始化哑节点头
dummy = Node(-1)
tail = dummy
while pq:
# 取出堆顶的最小节点
_, index, top = heapq.heappop(pq)
# 将该节点添加到结果链表
tail.next = top
tail = top
# 如果该节点存在后继节点,则将其加入堆
if top.next is not None:
heapq.heappush(pq, (top.next.data, index, top.next))
# 返回合并后的链表
return dummy.next
def printList(node):
while node is not None:
print(node.data, end="")
if node.next is not None:
print("->", end="")
node = node.next
print()
if __name__ == "__main__":
k = 3
arr = [None] * k
arr[0] = Node(1)
arr[0].next = Node(3)
arr[0].next.next = Node(5)
arr[0].next.next.next = Node(7)
arr[1] = Node(2)
arr[1].next = Node(4)
arr[1].next.next = Node(6)
arr[1].next.next.next = Node(8)
arr[2] = Node(0)
arr[2].next = Node(9)
arr[2].next.next = Node(10)
arr[2].next.next.next = Node(11)
head = mergeKLists(arr)
printList(head)
4. C#
cs
using System;
using System.Collections.Generic;
class Node {
public int data;
public Node next;
public Node(int x) {
data = x;
next = null;
}
}
// 最小堆使用的自定义比较器类
class NodeComparer : IComparer<Node> {
public int Compare(Node a, Node b) {
if (a.data > b.data)
return 1;
else if (a.data < b.data)
return -1;
return 0;
}
}
class DSA {
// 合并 K 个有序链表的函数
static Node mergeKLists(List<Node> arr) {
PriorityQueue<Node> pq = new PriorityQueue<Node>(new NodeComparer());
// 将 k 个链表的头节点插入堆中
foreach (Node head in arr) {
if (head != null) pq.Enqueue(head);
}
// 初始化哑节点头
Node dummy = new Node(-1);
Node tail = dummy;
while (pq.Count > 0) {
// 取出堆顶的最小节点
Node top = pq.Dequeue();
// 将该节点添加到结果链表
tail.next = top;
tail = top;
// 如果该节点存在后继节点,则将其加入堆
if (top.next != null) {
pq.Enqueue(top.next);
}
}
// 返回合并后的链表
return dummy.next;
}
static void printList(Node node) {
while (node != null) {
Console.Write(node.data);
if(node.next != null){
Console.Write("->");
}
node = node.next;
}
}
static void Main(string[] args) {
List<Node> arr = new List<Node>();
arr.Add(new Node(1));
arr[0].next = new Node(3);
arr[0].next.next = new Node(5);
arr[0].next.next.next = new Node(7);
arr.Add(new Node(2));
arr[1].next = new Node(4);
arr[1].next.next = new Node(6);
arr[1].next.next.next = new Node(8);
arr.Add(new Node(0));
arr[2].next = new Node(9);
arr[2].next.next = new Node(10);
arr[2].next.next.next = new Node(11);
Node head = mergeKLists(arr);
printList(head);
}
}
// 自定义优先队列(最小堆)
class PriorityQueue<T> {
private List<T> heap;
private IComparer<T> comparer;
public PriorityQueue(IComparer<T> comparer = null) {
this.heap = new List<T>();
this.comparer = comparer ?? Comparer<T>.Default;
}
public int Count => heap.Count;
// 入队操作
public void Enqueue(T item) {
heap.Add(item);
int i = heap.Count - 1;
while (i > 0) {
int parent = (i - 1) / 2;
if (comparer.Compare(heap[parent], heap[i]) <= 0)
break;
Swap(parent, i);
i = parent;
}
}
// 出队操作
public T Dequeue() {
if (heap.Count == 0)
throw new InvalidOperationException("Priority queue is empty.");
T result = heap[0];
int last = heap.Count - 1;
heap[0] = heap[last];
heap.RemoveAt(last);
last--;
int i = 0;
while (true) {
int left = 2 * i + 1;
if (left > last)
break;
int right = left + 1;
int minChild = left;
if (right <= last && comparer.Compare(heap[right], heap[left]) < 0)
minChild = right;
if (comparer.Compare(heap[i], heap[minChild]) <= 0)
break;
Swap(i, minChild);
i = minChild;
}
return result;
}
// 交换堆中两个元素
private void Swap(int i, int j) {
T temp = heap[i];
heap[i] = heap[j];
heap[j] = temp;
}
}
5. JavaScript
javascript
class Node {
constructor(x) {
this.data = x;
this.next = null;
}
}
// 用于最小堆的节点比较器
function nodeComparator(k1, k2) {
if (k1.data > k2.data) return -1;
if (k1.data < k2.data) return 1;
return 0;
}
// 优先队列(最小堆)实现
class PriorityQueue {
constructor(compare) {
this.heap = [];
this.compare = compare;
}
enqueue(value) {
this.heap.push(value);
this.bubbleUp();
}
bubbleUp() {
let index = this.heap.length - 1;
while (index > 0) {
let element = this.heap[index],
parentIndex = Math.floor((index - 1) / 2),
parent = this.heap[parentIndex];
if (this.compare(element, parent) < 0) break;
this.heap[index] = parent;
this.heap[parentIndex] = element;
index = parentIndex;
}
}
dequeue() {
let max = this.heap[0];
let end = this.heap.pop();
if (this.heap.length > 0) {
this.heap[0] = end;
this.sinkDown(0);
}
return max;
}
sinkDown(index) {
let left = 2 * index + 1,
right = 2 * index + 2,
largest = index;
if (
left < this.heap.length &&
this.compare(this.heap[left], this.heap[largest]) > 0
) {
largest = left;
}
if (
right < this.heap.length &&
this.compare(this.heap[right], this.heap[largest]) > 0
) {
largest = right;
}
if (largest !== index) {
[this.heap[largest], this.heap[index]] = [
this.heap[index],
this.heap[largest],
];
this.sinkDown(largest);
}
}
isEmpty() {
return this.heap.length === 0;
}
}
// 合并 K 个有序链表的函数
function mergeKLists(arr) {
const pq = new PriorityQueue(nodeComparator);
// 将 k 个链表的头节点插入堆中
for (let head of arr) {
if (head !== null) pq.enqueue(head);
}
// 初始化哑节点头
let dummy = new Node(-1);
let tail = dummy;
while (!pq.isEmpty()) {
// 取出堆顶的最小节点
let top = pq.dequeue();
// 将该节点添加到结果链表
tail.next = top;
tail = top;
// 如果该节点存在后继节点,则将其加入堆
if (top.next !== null) {
pq.enqueue(top.next);
}
}
// 返回合并后的链表
return dummy.next;
}
function printList(node) {
while (node !== null) {
process.stdout.write(node.data.toString());
if (node.next !== null) {
process.stdout.write("->");
}
node = node.next;
}
console.log();
}
let k = 3;
let arr = [];
arr[0] = new Node(1);
arr[0].next = new Node(3);
arr[0].next.next = new Node(5);
arr[0].next.next.next = new Node(7);
arr[1] = new Node(2);
arr[1].next = new Node(4);
arr[1].next.next = new Node(6);
arr[1].next.next.next = new Node(8);
arr[2] = new Node(0);
arr[2].next = new Node(9);
arr[2].next.next = new Node(10);
arr[2].next.next.next = new Node(11);
let head = mergeKLists(arr);
printList(head);
(三) 代码输出和算法复杂度
输出:
0->1->2->3->4->5->6->7->8->9->10->11
时间复杂度:O (n × log k),其中 n 是所有链表的节点总数。
空间复杂度:O (k),这是由堆占用的空间。堆中在任意时刻最多只存储 k 个元素。
五、【优化解法-2】使用分治法 ------ 时间复杂度 O (n),空间复杂度 O (n)
(一) 解法思路
该方法的核心思路是采用分治法:
- 递归地将 k 个链表分成两半,直到形成可以两两合并的链表对
- 使用二路归并(类似归并排序的合并步骤)将这些链表对合并
- 自底向上重复合并过程,直到所有链表合并为一个有序链表
分步实现思路:
- 将 k 个链表分成两部分:左半部分
lists[0...mid]和右半部分lists[mid+1...end]。 - 递归合并左半部分链表,得到第一个有序链表。
- 递归合并右半部分链表,得到第二个有序链表。
- 使用双指针法合并上述两个有序链表。
- 返回最终合并完成的有序链表。
(二) 使用 5 种语言实现
1. C++
cpp
#include <iostream>
#include <vector>
using namespace std;
class Node {
public:
int data;
Node* next;
Node(int x) {
data = x;
next = nullptr;
}
};
// 合并两个有序链表的函数
Node* mergeTwo(Node* head1, Node* head2) {
// 创建哑节点简化合并流程
Node* dummy = new Node(-1);
Node* curr = dummy;
// 同时遍历两个链表
while (head1 != nullptr && head2 != nullptr) {
// 将较小的节点加入合并后的链表
if (head1->data <= head2->data) {
curr->next = head1;
head1 = head1->next;
} else {
curr->next = head2;
head2 = head2->next;
}
curr = curr->next;
}
// 拼接剩余未遍历完的链表部分
if (head1 != nullptr) {
curr->next = head1;
} else {
curr->next = head2;
}
// 返回合并后的链表(跳过哑节点)
return dummy->next;
}
// 分治递归合并函数
Node* mergeListsRecur(int i, int j, vector<Node*> &arr) {
// 只剩一个链表时直接返回
if (i == j) return arr[i];
// 计算中间位置
int mid = i + (j-i)/2;
// 递归合并左半部分
Node* head1 = mergeListsRecur(i, mid, arr);
// 递归合并右半部分
Node* head2 = mergeListsRecur(mid+1, j, arr);
// 合并最终的两个有序链表
Node* head = mergeTwo(head1, head2);
return head;
}
// 合并 K 个有序链表的主函数
Node* mergeKLists(vector<Node*>& arr) {
// 空链表特殊处理
if (arr.size()==0) return nullptr;
return mergeListsRecur(0, arr.size()-1, arr);
}
// 打印链表
void printList(Node* node) {
while (node != nullptr) {
cout << node->data;
if(node->next)
cout<<" -> ";
node = node->next;
}
}
// 主函数测试
int main() {
int k = 3;
vector<Node*> arr(k);
arr[0] = new Node(1);
arr[0]->next = new Node(3);
arr[0]->next->next = new Node(5);
arr[0]->next->next->next = new Node(7);
arr[1] = new Node(2);
arr[1]->next = new Node(4);
arr[1]->next->next = new Node(6);
arr[1]->next->next->next = new Node(8);
arr[2] = new Node(0);
arr[2]->next = new Node(9);
arr[2]->next->next = new Node(10);
arr[2]->next->next->next = new Node(11);
Node* head = mergeKLists(arr);
printList(head);
return 0;
}
2. Java
java
import java.util.List;
class Node {
int data;
Node next;
Node(int x) {
data = x;
next = null;
}
}
class DSA {
// 合并两个有序链表
static Node mergeTwo(Node head1, Node head2) {
// 创建哑节点简化合并
Node dummy = new Node(-1);
Node curr = dummy;
// 双指针遍历两个链表
while (head1 != null && head2 != null) {
// 把较小节点接到结果上
if (head1.data <= head2.data) {
curr.next = head1;
head1 = head1.next;
} else {
curr.next = head2;
head2 = head2.next;
}
curr = curr.next;
}
// 拼接剩余部分
if (head1 != null) {
curr.next = head1;
} else {
curr.next = head2;
}
// 返回合并后的链表
return dummy.next;
}
// 分治递归合并
static Node mergeListsRecur(int i, int j, List<Node> arr) {
// 只剩一个链表直接返回
if (i == j) return arr.get(i);
// 找中点
int mid = i + (j - i) / 2;
// 递归合并左半部分
Node head1 = mergeListsRecur(i, mid, arr);
// 递归合并右半部分
Node head2 = mergeListsRecur(mid + 1, j, arr);
// 合并两个结果
Node head = mergeTwo(head1, head2);
return head;
}
// 合并 K 个有序链表主函数
static Node mergeKLists(List<Node> arr) {
// 空列表处理
if (arr.size() == 0) return null;
return mergeListsRecur(0, arr.size() - 1, arr);
}
// 打印链表
static void printList(Node node) {
while (node != null) {
System.out.print(node.data);
if(node.next!=null)
{
System.out.print(" -> ");
}
node = node.next;
}
}
public static void main(String[] args) {
int k = 3;
List<Node> arr = new java.util.ArrayList<>();
arr.add(new Node(1));
arr.get(0).next = new Node(3);
arr.get(0).next.next = new Node(5);
arr.get(0).next.next.next = new Node(7);
arr.add(new Node(2));
arr.get(1).next = new Node(4);
arr.get(1).next.next = new Node(6);
arr.get(1).next.next.next = new Node(8);
arr.add(new Node(0));
arr.get(2).next = new Node(9);
arr.get(2).next.next = new Node(10);
arr.get(2).next.next.next = new Node(11);
Node head = mergeKLists(arr);
printList(head);
}
}
3. Python
python
class Node:
def __init__(self, x):
self.data = x
self.next = None
# 合并两个有序链表的函数
def mergeTwo(head1, head2):
# 创建哑节点简化合并逻辑
dummy = Node(-1)
curr = dummy
# 双指针遍历两个链表
while head1 is not None and head2 is not None:
# 把较小的节点加入结果链表
if head1.data <= head2.data:
curr.next = head1
head1 = head1.next
else:
curr.next = head2
head2 = head2.next
curr = curr.next
# 拼接剩余未遍历完的部分
if head1 is not None:
curr.next = head1
else:
curr.next = head2
# 返回合并后的链表(跳过哑节点)
return dummy.next
# 分治递归合并主逻辑
def mergeListsRecur(i, j, arr):
# 只剩一个链表时直接返回
if i == j:
return arr[i]
# 计算中点,分割数组
mid = i + (j - i) // 2
# 递归合并左半边
head1 = mergeListsRecur(i, mid, arr)
# 递归合并右半边
head2 = mergeListsRecur(mid + 1, j, arr)
# 合并最终两个有序链表
head = mergeTwo(head1, head2)
return head
# 合并 K 个有序链表的入口函数
def mergeKLists(arr):
# 处理空链表情况
if len(arr) == 0:
return None
return mergeListsRecur(0, len(arr) - 1, arr)
# 打印链表
def printList(node):
while node is not None:
print(f"{node.data}", end="")
if node.next is not None:
print(" -> ", end="")
node = node.next
print()
# 测试主程序
if __name__ == "__main__":
k = 3
arr = [None] * k
arr[0] = Node(1)
arr[0].next = Node(3)
arr[0].next.next = Node(5)
arr[0].next.next.next = Node(7)
arr[1] = Node(2)
arr[1].next = Node(4)
arr[1].next.next = Node(6)
arr[1].next.next.next = Node(8)
arr[2] = Node(0)
arr[2].next = Node(9)
arr[2].next.next = Node(10)
arr[2].next.next.next = Node(11)
head = mergeKLists(arr)
printList(head)
4. C#
cs
using System;
using System.Collections.Generic;
class Node {
public int data;
public Node next;
public Node(int x) {
data = x;
next = null;
}
}
class DSA {
// 合并两个有序链表
static Node mergeTwo(Node head1, Node head2) {
// 创建哑节点简化合并
Node dummy = new Node(-1);
Node curr = dummy;
// 双指针遍历两个链表
while (head1 != null && head2 != null) {
// 把较小节点接到结果上
if (head1.data <= head2.data) {
curr.next = head1;
head1 = head1.next;
} else {
curr.next = head2;
head2 = head2.next;
}
curr = curr.next;
}
// 拼接剩余部分
if (head1 != null) {
curr.next = head1;
} else {
curr.next = head2;
}
// 返回合并后的链表
return dummy.next;
}
// 分治递归合并
static Node mergeListsRecur(int i, int j, List<Node> arr) {
// 只剩一个链表直接返回
if (i == j) return arr[i];
// 找中点
int mid = i + (j - i) / 2;
// 递归合并左半部分
Node head1 = mergeListsRecur(i, mid, arr);
// 递归合并右半部分
Node head2 = mergeListsRecur(mid + 1, j, arr);
// 合并两个结果
Node head = mergeTwo(head1, head2);
return head;
}
// 合并 K 个有序链表主函数
static Node mergeKLists(List<Node> arr) {
// 空列表处理
if (arr.Count == 0) return null;
return mergeListsRecur(0, arr.Count - 1, arr);
}
// 打印链表
static void printList(Node head) {
Node temp = head;
while (temp != null) {
Console.Write(temp.data);
if(temp.next != null)
Console.Write(" -> ");
temp = temp.next;
}
Console.WriteLine();
}
static void Main(string[] args) {
List<Node> arr = new List<Node>();
arr.Add(new Node(1));
arr[0].next = new Node(3);
arr[0].next.next = new Node(5);
arr[0].next.next.next = new Node(7);
arr.Add(new Node(2));
arr[1].next = new Node(4);
arr[1].next.next = new Node(6);
arr[1].next.next.next = new Node(8);
arr.Add(new Node(0));
arr[2].next = new Node(9);
arr[2].next.next = new Node(10);
arr[2].next.next.next = new Node(11);
Node head = mergeKLists(arr);
printList(head);
}
}
5. JavaScript
javascript
class Node {
constructor(x) {
this.data = x;
this.next = null;
}
}
// 合并两个有序链表
function mergeTwo(head1, head2) {
// 创建哑节点,方便拼接
let dummy = new Node(-1);
let curr = dummy;
// 双指针遍历两个链表
while (head1 !== null && head2 !== null) {
// 选择较小节点加入结果
if (head1.data <= head2.data) {
curr.next = head1;
head1 = head1.next;
} else {
curr.next = head2;
head2 = head2.next;
}
curr = curr.next;
}
// 拼接剩余部分
if (head1 !== null) {
curr.next = head1;
} else {
curr.next = head2;
}
// 返回合并后的链表
return dummy.next;
}
// 分治递归:合并区间 [i, j] 的所有链表
function mergeListsRecur(i, j, arr) {
// 只剩一个链表,直接返回
if (i === j) return arr[i];
// 找中点
let mid = i + Math.floor((j - i) / 2);
// 递归合并左半部分
let head1 = mergeListsRecur(i, mid, arr);
// 递归合并右半部分
let head2 = mergeListsRecur(mid + 1, j, arr);
// 合并两个有序链表
return mergeTwo(head1, head2);
}
// 主函数:合并 K 个有序链表
function mergeKLists(arr) {
// 空数组处理
if (arr.length === 0) return null;
return mergeListsRecur(0, arr.length - 1, arr);
}
// 打印链表
function printList(node) {
while (node !== null) {
process.stdout.write(node.data.toString());
if (node.next !== null) {
process.stdout.write(" -> ");
}
node = node.next;
}
}
// 测试用例
let k = 3;
let arr = [];
arr[0] = new Node(1);
arr[0].next = new Node(3);
arr[0].next.next = new Node(5);
arr[0].next.next.next = new Node(7);
arr[1] = new Node(2);
arr[1].next = new Node(4);
arr[1].next.next = new Node(6);
arr[1].next.next.next = new Node(8);
arr[2] = new Node(0);
arr[2].next = new Node(9);
arr[2].next.next = new Node(10);
arr[2].next.next.next = new Node(11);
let head = mergeKLists(arr);
printList(head);
(三) 代码输出和算法复杂度
输出:
0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11
时间复杂度:O (n × k × log k),其中 n 是最长链表中的节点数量。
空间复杂度:O (log k),由递归调用栈占用。
