题目
思路
除了暴力求解,这道题有两条思路:利用最小堆和分而治之思想。
最小堆求解
由于这道题是将k个链表合并,并且这些链表都已经是有序的链表了,直觉告诉我们肯定是依次取它们的头节点进行比较,然后按从小到大的次序插入到新的头节点中。
那么堆结构就能够满足这样的需求,首先将k个链表的头节点构建成一个堆结构,然后自下而上地调整这样的堆使得最小的一个值在根节点,此时取出那个根节点作为第一个最小值节点。
那么剩下要做的就是给这个被破坏的堆重新增加一个节点,这时又要考虑两种情况:一种是当k个链表的节点都已经是堆中的节点时,那么被拿去的根节点就需要用最后的叶子节点来更换;另一种是K个链表的节点还没有初次遍历完,那么直接将新遍历到的节点换上被拿去的根节点。然后自上而下调整堆结构就好。
最小堆的构建依照完全二叉树的特性,可以使用2倍关系找到其父节点或孩子节点。一开始自下而上调整,从最后一个父节点开始调整,具体实现可以看一下的代码。
时间复杂度O(nk*logk) 空间复杂度O(k)
代码
Class Solution{
public:
void makeHeap(vector<ListNode*> &heap){
int para_last = (heap.size()-1)/2;
for (int i = para_last; i >= 0 ; i--){
minHeap(heap, i);
}
}
void minHeap(vector<ListNode*> &heap, int i){
int left = 2*i; // 找到父节点i的子节点
int right = left + 1;
int least = i; // 假设最小值节点为i
//得到最小值所在的vector索引
if(left < heap.size() && heap[left]->val < heap[i]->val)
least = left;
if(right < heap.size() && heap[right]->val < heap[least]->val)
least = right;
if(least!=i){
swap(heap[i],heap[least]);
minHeap(heap, least);// 如果该交换打乱了下面的顺序,那就要继续为以下的进行堆排序。
}
}
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.empty()) return NULL;
vector<ListNode*> heap;//最小堆
ListNode *result = new ListNode(0);
//将每个链表的头节点插入heap
for (int i = 0; i < lists.size(); ++i){
if(lists[i]!=NULL)
heap.push_back(lists[i]);
}
makeHeap(heap);//建立初始的最小堆
ListNode *p = result;
while(!heap.empty()){
// 将堆顶插入到p中
ListNode *top = heap[0];
cout<<top->val<<endl;
p -> next = top;
p = p -> next;
ListNode* next = top -> next;
if(next!=NULL){
//如果该链表还有元素,那就将其接上堆顶
heap[0] = next;
}else{
//如果该链表没有元素,那就将堆最后的一个元素插入堆顶
swap(heap[0],heap[heap.size()-1]);
//并删除堆中最后一个元素
heap.pop_back();
}
//重新调整堆
minHeap(heap,0);
}
return result -> next;
}
}
分而治之思想
想到分而治之思想的话就可以利用leetcode21的题解做分的操作,然后只需要在mergerK里面写上合的步骤。一开始需要设置一个结尾的指针,用来监控分治的进度,需要考虑的是,什么时候分治停止。
停止就是当这个结尾指针等于0的时候,那么只要该结尾指针还大于0,就需要不断分治,例如两个链表a和b,通过merge2算法得到新链表并赋值给a,这样a就更长了。依次类推不断分治,知道end为0。
时间复杂度O(nk*logk) 空间复杂度O(1)
代码
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.empty()) return NULL;
int end = lists.size()-1;//设置尾部指针
while(end > 0){//当尾部指针还未到达0时,说明分支算法还未结束
int begin = 0;
while(begin < end){//当一波分治未结束时
lists[begin] = mergeTwoLists(lists[begin],lists[end]);
begin ++;
end --;
}
}
return lists[0];
}
private:
//该算法就是leetcode21的solution
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (!l1) return l2; // list 1 is empty
if (!l2) return l1; // list 2 is empty
ListNode *dummy = new ListNode(0);
ListNode *currentTail = dummy; // current tail of the new list
while (l1 && l2){
if (l1->val <= l2->val){
currentTail->next = l1;
currentTail = l1;
if (l1->next)
l1 = l1->next;
else{
l1->next = l2;
return dummy->next;
}
}else {
currentTail->next = l2;
currentTail = l2;
if (l2->next)
l2 = l2->next;
else{
l2->next = l1;
return dummy->next;
}
}
}
}
};