23合并K个排序链表(归并+堆、逐一比较、分治法)

1、题目描述

合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。

2、示例

输入:
[
  1->4->5,
  1->3->4,
  2->6
]
输出: 1->1->2->3->4->4->5->6

3、题解

解法:归并+堆,(23合并K个排序链表、合并K个有序数组、373查找和最小的K对数字378有序矩阵中第 K 小的元素215数组中的第K个最大元素

基本思想:归并+堆,维护一个堆,堆中为每个list的当前最小的节点,堆大小lists.size,不断从堆中取出最小的节点,取出最小的节点后,加入该节点所在list的下一个节点入堆

解法一:逐一比较

比较k个节点(每个链表的首节点),获得最小值的节点,将选中的节点接在最终有序链表res的后面。时间复杂度O(k*N)548ms

  • flag标记已经有几个链表到表尾NULL扫描完了,达到k循环结束,表示k个链表都扫描完了
  • 一次比较k个链表的首节点找到最小的节点的下标j和最小值minVal
  • 找到最小的节点lists[j]放入最终有序链表res后面,且该节点指针指向下一个节点lists[j]=lists[j]->next
  • 如果下一个节点为空,说明该链表扫描完为空了flag++

解法二:分治法

使用双指针,i=0指向lists头,j=k-1指向lists尾,不断i++,j--比较两个链表合并成一个有序链表,最终合并成一个有序链表res返回。时间复杂度O(N*logk)20ms

  • 不断合并两个链表,并将合并的有序链表存放在i节点,相当于k长度链表合并成k/2个链表保存在lists前k/2位置,然后i=0再重新合并i=0到j=k/2之间的链表存放入0到k/4lists位置
#include<iostream>
#include<vector>
using namespace std;
struct ListNode {
	int val;
	ListNode* next;
	ListNode(int x) : val(x), next(NULL) {}
};
class Solution {
public:
    struct Node{
        ListNode* ptr;
        Node(){}
        Node(ListNode* ptr):ptr(ptr){}
        bool operator>(const Node& a)const{return this->ptr->val>a.ptr->val;}
    };
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        ListNode* head=new ListNode();
        ListNode* p=head;
        priority_queue<Node,vector<Node>,greater<Node>> min_heap;
        for(int i=0;i<lists.size();i++)
        {
            if(lists[i])
                min_heap.push(Node(lists[i]));
        }
        while(!min_heap.empty())
        {
            Node cur=min_heap.top();
            min_heap.pop();
            p->next=cur.ptr;
            p=p->next;
            if(cur.ptr->next)
            {
                cur.ptr=cur.ptr->next;
                min_heap.push(cur);
            }
        }
        return head->next;
    }
};
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
		//基本思想:归并+堆,维护一个堆,堆中为每个list的当前最小的节点,堆大小lists.size,
		//不断从堆中取出最小的节点,取出最小的节点后,加入该节点所在list的下一个节点入堆
        ListNode* head=new ListNode();
        ListNode* p=head;
        vector<ListNode*> min_heap;
        for(int i=0;i<lists.size();i++)
        {
            if(lists[i])
            {
                min_heap.push_back(lists[i]);
                push_heap(min_heap.begin(),min_heap.end(),[](ListNode* &a,ListNode* &b){return a->val>b->val;});
            } 
        }
        while(!min_heap.empty())
        {
            ListNode* cur=min_heap[0];
            pop_heap(min_heap.begin(),min_heap.end(),[](ListNode* &a,ListNode* &b){return a->val>b->val;});
            min_heap.pop_back();
            p->next=cur;
            p=p->next;
            cur=cur->next;
            if(cur)
            {
                min_heap.push_back(cur);
                push_heap(min_heap.begin(),min_heap.end(),[](ListNode* &a,ListNode* &b){return a->val>b->val;});
            }
        }
        return head->next;

    }
};
//解法一:逐一比较
//比较k个节点(每个链表的首节点),获得最小值的节点,将选中的节点接在最终有序链表res的后面。时间复杂度O(k*N)
class Solution1 {
public:
	ListNode* mergeKLists(vector<ListNode*>& lists) {
		int k = lists.size();
		ListNode* res, * head = new ListNode(0);
		res = head;
		if (k == 0)
			return res->next;
		int i, j = 0, flag = 0, minVal;  //j标记一次比较k个链表的首节点最小的节点的下标,minVal标记最小节点值val
		//flag标记已经有几个链表到表尾NULL扫描完了,达到k循环结束,表示k个链表都扫描完了
		while (flag < k)
		{
			minVal = 6553500;
			//一次比较k个链表的首节点找到最小的节点的下标j和最小值minVal
			for (i = 0; i < k; i++)
			{
				if (lists[i] != NULL && lists[i]->val < minVal)
				{
					minVal = lists[i]->val;
					j = i;
				}
			}
			//对于所有k个链表都为NULL的情况,如果有lists[j]为空,表示lists[0]为空且其它链表也为空,不断flag++达到k大小返回NULL
			if (lists[j] == NULL)
			{
				flag++;
				continue;
			}
			//找到最小的节点lists[j]放入最终有序链表res后面,且该节点指针指向下一个节点lists[j]=lists[j]->next
			head->next = lists[j];
			head = lists[j];
			lists[j] = lists[j]->next;
			//如果下一个节点为空,说明该链表扫描完为空了flag++
			if (lists[j] == NULL)
				flag++;
		}
		return res->next;
	}
};
//解法二:分治思想
//使用双指针,i=0指向lists头,j=k-1指向lists尾,不断i++,j--比较两个链表合并成一个有序链表,最终合并成一个有序链表res返回。时间复杂度O(N*logk)
class Solution2 {
public:
	ListNode* mergeKLists(vector<ListNode*>& lists) {
		int k = lists.size();
		if (k == 0)
			return NULL;
		int i = 0, j = k - 1; 
		while (j > 0)
		{
			//不断合并两个链表,并将合并的有序链表存放在i节点,相当于k长度链表合并成k/2个链表保存在lists前k/2位置,然后i=0再重新合并i=0到j=k/2之间的链表存放入0到k/4lists位置
			while (i < j)
			{
				lists[i] = mergeTwoLists(lists[i], lists[j]);
				i++;
				j--;
			}
			i = 0;
		}
		
		return lists[0];
	}
	//合并两个链表成一个有序链表
	ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
		ListNode* l3, * head;
		l3 = new ListNode(0);
		head = l3;
		//当两个链表都非空时:如果节点l1<=节点,将节点l1加到l3后l1到下一节点l1=l1->next,否则将节点l2加到l3后l2到下一节点l2=l2->next
		while (l1 != NULL && l2 != NULL)
		{
			if (l1->val <= l2->val)
			{
				l3->next = l1;
				l1 = l1->next;
			}
			else
			{
				l3->next = l2;
				l2 = l2->next;
			}
			l3 = l3->next;
		}
		//当一个链表为空了,另一个非空链表,直接加到l3后面
		if (l1 == NULL)
			l3->next = l2;
		else
			l3->next = l1;
		return head->next;
	}
};
void CreateList(ListNode* head, int n)
{
	ListNode* p;
	for (int i = 1; i <= n; i++)
	{
		p = new ListNode(i);
		head->next = p;
		head = p;
	}
}
int main()
{
	Solution2 solute;
	int n = 3;
	vector<ListNode*> lists;
	ListNode* head = new ListNode(0), * res;
	CreateList(head, n);
	lists.push_back(head->next);
	head = new ListNode(0);
	CreateList(head, n);
	lists.push_back(head->next);
	res = solute.mergeKLists(lists);
	while (res != NULL)
	{
		cout << res->val << endl;
		res = res->next;
	}
	return 0;
}

 

 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值