LeetCode023——合并K个排序链表

本文介绍LeetCode上合并K个有序链表问题的五种解法,包括使用归并排序、Java PriorityQueue实现堆排序等,并分析每种方法的时间和空间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我的LeetCode代码仓:https://github.com/617076674/LeetCode

原题链接:https://leetcode-cn.com/problems/merge-k-sorted-lists/description/

题目描述:

知识点:链表,归并排序

思路一:和LeetCode021——合并两个有序链表同样的思路

既然我们能用归并排序的思路合并两个有序链表,我们当然也能用归并排序的思路合并k个有序链表。但是循环过程中需要额外注意以下几点:

(1)如果输入的lists数组中没有元素,或者有元素但是每一个元素都是null,直接返回null

(2)在k个指针中寻找最小值节点时要注意指向一些链表的指针可能已经为null。

(3)循环的结束条件是,k个指针均为空。

(4)设立虚拟头节点dummyHead记录结果。

由于要遍历每一个链表中的每一个节点,而在寻找下一个节点的时候又要和其他所有非空节点的值做比较,因此时间复杂度是O(n * m)级别的,其中n为数组中所有链表的节点总数,而m为输入的lists数组的长度。对于链表的操作,只涉及到指针,但我们新建了一个长度为n链表,因此空间复杂度为O(n)。

JAVA代码:

public class Solution {

    public ListNode mergeKLists(ListNode[] lists) {
		ListNode[] curs = new ListNode[lists.length];
		for (int i = 0; i < curs.length; i++) {
			curs[i] = lists[i];
		}
		ListNode dummyHead = new ListNode(-1);
		ListNode cur = dummyHead;
		boolean flag = true;
		do {
			int index = 0;
			for(int i = 0; i < curs.length; i++) {
				if(curs[i] != null) {
					break;
				}
				index++;
			}
			//如果输入的lists数组中没有元素,或者有元素但是每一个元素都是null,直接返回null
            if(index == curs.length) {
				return null;
			}
			ListNode minTemp = curs[index];
			for(int i = index; i < curs.length; i++) {
				if(curs[i] != null && curs[i].val < minTemp.val) {
					minTemp = curs[i];
				}
			}
			for(int i = index; i < curs.length; i++) {
				if(curs[i] != null && minTemp.val == curs[i].val) {
					curs[i] = curs[i].next;
					break;
				}
			}
			cur.next = minTemp;
			cur = cur.next;
			flag = false;
			for (int i = 0; i < curs.length; i++) {
				if(curs[i] != null) {
					flag = true;
				}
			}
		}while(flag);
		return dummyHead.next;
	}
}

LeetCode解题报告:

思路二:利用Java库函数中的PriorityQueue实现对所有节点值的堆排序

Java中的PriorityQueue默认实现的就是一个最小堆,正好满足我们题目要求。我们只需要遍历每一个链表中每一个节点的值,将其加入最小堆中,再从堆中依次取出最小元素组成一个新的链表即可。

n个加入堆的时间复杂度是O(nlogn)级别的,其中n为数组中所有链表的节点总数。其实这相当于是一个排序过程,只不过我们用的是堆排序实现的。出堆的时间复杂度也是O(nlogn)级别的,因此总的时间复杂度是O(nlogn)级别的。我们新建了一个长度为n链表,因此空间复杂度为O(n)。

JAVA代码:

public class Solution {

	public ListNode mergeKLists(ListNode[] lists) {
		PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
		for (int i = 0; i < lists.length; i++) {
			ListNode cur = lists[i];
			while(cur != null) {
				priorityQueue.add(cur.val);
				cur = cur.next;
			}
		}
		ListNode dummyHead = new ListNode(-1);
		ListNode cur = dummyHead;
		while(!priorityQueue.isEmpty()) {
			cur.next = new ListNode(priorityQueue.poll());
			cur = cur.next;
		}
		return dummyHead.next;
	}
}

LeetCode解题报告:

思路三:利用Java库函数中的PriorityQueue实现对所有链表中当前节点的堆排序

在思路二中,我们把所有链表中所有节点的值都一起入堆,使得我们堆的规模达到了n,其中n为数组中所有链表的节点总数。我们完全可以每次维持堆中的元素最多为k个,其中k为lists数组中各个链表所遍历到的当前节点的非空节点数。

由于要根据出堆的元素寻找入堆的下一个节点,因此我们堆中保存的是ListNode类型,而不是Integer类型。当然这么做,我们就要为PriorityQueue定义一个新的比较器,这都是Java语言的知识点,不再赘述。

有n个元素需要入堆和出堆操作,而我们堆的大小最多为k,因此我们的时间复杂度是O(nlogk)。而空间复杂度由两部分组成,一部分是堆,其空间复杂度为O(k),另一部分是新建的一个长度为n链表,空间复杂度为O(n)。总的空间复杂度是O(n)。

JAVA代码:

public class Solution {

	public ListNode mergeKLists(ListNode[] lists) {
		if(lists.length == 0) {
			return null;
		}
		//该构造方法要求lists.length大于等于1
		PriorityQueue<ListNode> priorityQueue = new PriorityQueue<>(lists.length, new Comparator<ListNode>() {

			@Override
			public int compare(ListNode node1, ListNode node2) {
				return node1.val - node2.val;
			}
		});
		for (int i = 0; i < lists.length; i++) {
			if(lists[i] != null) {
				priorityQueue.add(lists[i]);
			}
		}
		ListNode dummyHead = new ListNode(-1);
		ListNode cur = dummyHead;
		while(!priorityQueue.isEmpty()) {
			ListNode temp = priorityQueue.poll();
			if(temp.next != null) {
				priorityQueue.add(temp.next);
			}
			cur.next = temp;
			cur = cur.next;	
		}
		return dummyHead.next;
	}
}

LeetCode解题报告:

思路四:对两个链表向右一次次归并排序

这个思路很简单,就是先对数组lists中的索引为0的链表和索引为1的链表进行归并排序,得到结果temp,再对temp链表和索引为2的链表进行归并排序,依次类推,直到得到最终结果。

时间复杂度和思路一一样,是O(n * m)级别的,其中n为数组中所有链表的节点总数,而m为输入的lists数组的长度。空间复杂度也和思路一一样,是O(n)级别的。

JAVA代码:

public class Solution {

	public ListNode mergeKLists(ListNode[] lists) {
		if(lists.length == 0) {
			return null;
		}
		if(lists.length == 1) {
			return lists[0];
		}
		ListNode temp = mergeTwoLists(lists[0], lists[1]);
		for(int i = 2; i < lists.length; i++) {
			temp = mergeTwoLists(temp, lists[i]);
		}
		return temp;
	}
	
	private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
		ListNode cur1 = l1;
		ListNode cur2 = l2;
		ListNode dummyHead = new ListNode(-1);
		ListNode cur = dummyHead;
		while(cur1 != null || cur2 != null) {
			if(cur1 == null) {
				cur.next = cur2;
				cur2 = cur2.next;
			}else if(cur2 == null) {
				cur.next = cur1;
				cur1 = cur1.next;
			}else if(cur1.val > cur2.val) {
				cur.next = cur2;
				cur2 = cur2.next;
			}else {
				cur.next = cur1;
				cur1 = cur1.next;
			}
			cur = cur.next;
		}
		return dummyHead.next;
	}
}

LeetCode解题报告:

思路五:两两归并排序,自底向上的实现

思路一中我们是对所有链表一起进行归并排序操作,思路四中我们是对链表数组中的链表从左往右逐渐右移合并,而其实更进一步地,我们完全可以两两归并排序,就相当于总共进行了lists.length - 1次归并排序的过程。

其时间复杂度是O(nlogm)级别的,其中n为数组中所有链表的节点总数,而m为输入的lists数组的长度。由于我们总共新建了O(logm)个新数组,而每个数组所有链表的总节点数都是n,因此空间复杂度是O(nlogm)。

JAVA代码:

public class Solution {

	public ListNode mergeKLists(ListNode[] lists) {
		while(lists.length > 1) {
			if(lists.length % 2 == 0) {
				ListNode[] newLists = new ListNode[lists.length / 2];
				int k = 0;
				for (int i = 0; i < lists.length - 1; i += 2) {
					newLists[k++] = mergeTwoLists(lists[i], lists[i + 1]);
				}
				lists = newLists;
			}else {
				ListNode[] newLists = new ListNode[(lists.length - 1) / 2 + 1];
				int k = 0;
				for (int i = 0; i < lists.length - 2; i += 2) {
					newLists[k++] = mergeTwoLists(lists[i], lists[i + 1]);
				}
				newLists[k] = lists[lists.length - 1];
				lists = newLists;
			}
		}
		if(lists.length == 0) {
			return null;
		}
		return lists[0];
	}

	private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
		ListNode cur1 = l1;
		ListNode cur2 = l2;
		ListNode dummyHead = new ListNode(-1);
		ListNode cur = dummyHead;
		while(cur1 != null || cur2 != null) {
			if(cur1 == null) {
				cur.next = cur2;
				cur2 = cur2.next;
			}else if(cur2 == null) {
				cur.next = cur1;
				cur1 = cur1.next;
			}else if(cur1.val > cur2.val) {
				cur.next = cur2;
				cur2 = cur2.next;
			}else {
				cur.next = cur1;
				cur1 = cur1.next;
			}
			cur = cur.next;
		}
		return dummyHead.next;
	}
}

LeetCode解题报告:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值