LeetCode 面试经典 150_分治_合并 K 个升序链表(108_23_C++_困难)
题目描述:
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
输入输出样例:
示例 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
题解:
解题思路:
思路一(分治合并(非递归)):
1、通过每趟两两归并,最终合并成一个单链表。
例 :
list1~list5代表 五个单链表
lists={list1,list2,list3,list4,list5}
① lists={ [list1,list3,list5] ,list4,list5} 合并list1,list2=>list1,合并list3,list4=>list3。
② lists={ [list1,list5] ,list5,list4,list5} 合并list1,list3=>list1。
③ lists={ [list1] ,list5,list5,list4,list5} 合并list1,list5=>list1,合并到只有一个链表时结束。
此思想非常类似于哈夫曼编码,尽量使短的链表进行合并,节省操作的时间。(每次将合并后的数据存储在数组头部,每次对合并好的元素再进行二次合并,再次存储在头部)
2、复杂度分析:
① 时间复杂度: O(kn×logk),假设每个链表的最长长度是 n,有k 个链表。第一轮合并 k/2 组链表,每一组的时间代价是 O(2n);第二轮合并 k/4 组链表,每一组的时间代价是 O(4n)...所以总的时间代价是O(kn×logk),故渐进时间复杂度为 O(kn×logk)。
② 空间复杂度:O(1)。
代码实现
代码实现(思路一(合并后排序)):
cpp
class Solution {
private:
// 合并两个有序链表的辅助函数
ListNode* merge(ListNode* head1, ListNode* head2) {
// 如果其中一个链表为空,直接返回另一个链表
if(head1 == nullptr || head2 == nullptr)
return head1 ? head1 : head2;
// 创建一个虚拟头结点,方便处理合并链表
ListNode* dummyHead = new ListNode();
ListNode* mergeTail = dummyHead; // mergeTail 用于跟踪合并链表的最后一个节点
// 开始合并两个链表,直到有一个链表遍历完
while(head1 != nullptr && head2 != nullptr) {
// 比较两个链表当前节点的值
if(head1->val < head2->val) {
// 将较小的节点添加到合并链表中
mergeTail->next = head1;
head1 = head1->next; // 移动 head1 指针到下一个节点
} else {
// 同样操作将 head2 的节点添加
mergeTail->next = head2;
head2 = head2->next; // 移动 head2 指针到下一个节点
}
mergeTail = mergeTail->next; // 更新合并链表的尾部
}
// 处理剩余节点(只会有一个链表还有剩余)
if(head1 != nullptr) {
mergeTail->next = head1; // 将剩余的 head1 链表连到合并链表后
} else {
mergeTail->next = head2; // 将剩余的 head2 链表连到合并链表后
}
// 返回合并后的链表,跳过虚拟头结点
ListNode* result = dummyHead->next;
delete dummyHead; // 释放虚拟头结点
return result; // 返回合并后的链表头
}
public:
// 合并 K 个有序链表的主函数
ListNode* mergeKLists(vector<ListNode*>& lists) {
int len = lists.size(); // 获取链表数组的大小
// 如果没有链表,则直接返回 nullptr
if(len == 0){
return nullptr;
}
// 循环合并链表,直到只剩下一个链表
while(len > 1) {
int k = 0; // k 用于跟踪合并后的链表数量
// 每次将相邻的两个链表合并
for(int i = 0; i < len; i += 2) {
if (i + 1 < len) {
// 如果还有相邻的两个链表,则合并
lists[k] = merge(lists[i], lists[i + 1]);
} else {
// 如果奇数个链表,最后一个链表不合并,直接保留
lists[k] = lists[i];
}
k++; // 对合并后的链表数量进行增加
}
len = k; // 更新当前链表数量(合并后的数量)
}
// 返回最终合并后的链表
return lists[0];
}
};
以思路一为例进行调试
cpp
#include<iostream>
#include<vector>
using namespace std;
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
//尾插法创建单链表
ListNode *createList(vector<int> arr){
ListNode *head=nullptr,*tail=nullptr;
for(const auto &val:arr){
if(head==nullptr){
head=tail=new ListNode(val);
}else{
tail->next=new ListNode(val);
tail=tail->next;
}
}
return head;
}
class Solution {
private:
// 合并两个有序链表的辅助函数
ListNode* merge(ListNode* head1, ListNode* head2) {
// 如果其中一个链表为空,直接返回另一个链表
if(head1 == nullptr || head2 == nullptr)
return head1 ? head1 : head2;
// 创建一个虚拟头结点,方便处理合并链表
ListNode* dummyHead = new ListNode();
ListNode* mergeTail = dummyHead; // mergeTail 用于跟踪合并链表的最后一个节点
// 开始合并两个链表,直到有一个链表遍历完
while(head1 != nullptr && head2 != nullptr) {
// 比较两个链表当前节点的值
if(head1->val < head2->val) {
// 将较小的节点添加到合并链表中
mergeTail->next = head1;
head1 = head1->next; // 移动 head1 指针到下一个节点
} else {
// 同样操作将 head2 的节点添加
mergeTail->next = head2;
head2 = head2->next; // 移动 head2 指针到下一个节点
}
mergeTail = mergeTail->next; // 更新合并链表的尾部
}
// 处理剩余节点(只会有一个链表还有剩余)
if(head1 != nullptr) {
mergeTail->next = head1; // 将剩余的 head1 链表连到合并链表后
} else {
mergeTail->next = head2; // 将剩余的 head2 链表连到合并链表后
}
// 返回合并后的链表,跳过虚拟头结点
ListNode* result = dummyHead->next;
delete dummyHead; // 释放虚拟头结点
return result; // 返回合并后的链表头
}
public:
// 合并 K 个有序链表的主函数
ListNode* mergeKLists(vector<ListNode*>& lists) {
int len = lists.size(); // 获取链表数组的大小
// 如果没有链表,则直接返回 nullptr
if(len == 0){
return nullptr;
}
// 循环合并链表,直到只剩下一个链表
while(len > 1) {
int k = 0; // k 用于跟踪合并后的链表数量
// 每次将相邻的两个链表合并
for(int i = 0; i < len; i += 2) {
if (i + 1 < len) {
// 如果还有相邻的两个链表,则合并
lists[k] = merge(lists[i], lists[i + 1]);
} else {
// 如果奇数个链表,最后一个链表不合并,直接保留
lists[k] = lists[i];
}
k++; // 对合并后的链表数量进行增加
}
len = k; // 更新当前链表数量(合并后的数量)
}
// 返回最终合并后的链表
return lists[0];
}
};
int main(){
vector<vector<int>> a={{1,4,5},{1,3,4},{2,6}};
vector<ListNode *> lists;
//将a容器中的数据转换成单链表
for(auto const &list:a){
ListNode *head=createList(list);
lists.emplace_back(head);
}
Solution s;
//进行各个单链表合并
ListNode *ans=s.mergeKLists(lists);
//输出合并的单链表
while(ans!=nullptr){
cout<<ans->val<<"->";
ans=ans->next;
}
cout<<"NULL";
return 0;
}
LeetCode 面试经典 150_分治_合并 K 个升序链表(108_23)原题链接
欢迎大家和我沟通交流(✿◠‿◠)