我的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解题报告: