java合并链表为一个升序链表_leetcode 23. 合并K个排序链表(Java版)

该博客介绍了如何使用Java合并多个已排序的链表,以创建一个升序的链表。提供了两种解法:一种是利用优先队列(小根堆),时间复杂度为O(n log k),空间复杂度为O(k);另一种是通过类似归并排序的回溯过程,时间复杂度为O(n log k),空间复杂度为O(1)。博客中还包含了详细的代码实现和思路解析。
摘要由CSDN通过智能技术生成

题目描述(题目难度,困难)

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

示例:

输入:

[

1->4->5,

1->3->4,

2->6

]

输出: 1->1->2->3->4->4->5->6

示例代码

解法一:

借助优先队列(小根堆),下面解法的时间复杂度为

math?formula=O(n%5Clog(k)),k 为链表个数,n 为总的结点数,空间复杂度为

math?formula=O(k),小根堆需要维护一个长度为 k 的数组。

时间复杂度分析:有 k 个结点的完全二叉树,高度为

math?formula=%5Clfloor%5Clog(k)%5Crfloor%2B1,每次弹出堆顶元素和插入元素重新调整堆的时间复杂度都为

math?formula=O(%5Clog(k)),所以总的时间复杂度为

math?formula=O(n%5Clog(k))。分析的比较粗略,不是精确的时间复杂度,不过大 O 表示法本身就是一个粗略的量级的时间复杂度表示,这样就足够了。

/**

* Definition for singly-linked list.

* public class ListNode {

* int val;

* ListNode next;

* ListNode(int x) { val = x; }

* }

*/

class Solution {

public ListNode mergeKLists(ListNode[] lists) {

PriorityQueue pq = new PriorityQueue<>(new Comparator() {

@Override

public int compare(ListNode o1, ListNode o2) {

return o1.val-o2.val;

}

});

for(ListNode e : lists){

if(e != null){

pq.add(e);

}

}

ListNode head = new ListNode(0);

head.next = null;

ListNode tail = head;

while(!pq.isEmpty()){

tail.next = pq.poll();

tail = tail.next;

if(tail.next != null){

pq.add(tail.next);

}

tail.next = null;

}

return head.next;

}

}

解法二:

类似归并排序的回溯过程,两两合并。下面解法的时间复杂度也为

math?formula=O(n%5Clog(k)),k 为链表个数,n 为总的结点数,空间复杂度为

math?formula=O(1)

时间复杂度分析:两两归并,每个结点会被归并

math?formula=%5Clog(k) 次,所以总的时间复杂度为

math?formula=O(n%5Clog(k))

/**

* Definition for singly-linked list.

* public class ListNode {

* int val;

* ListNode next;

* ListNode(int x) { val = x; }

* }

*/

class Solution {

// 借助归并排序的思想,只有治没有分

public ListNode merge(ListNode l1, ListNode l2){

ListNode res = new ListNode(0);

ListNode tail = res;

while(l1 != null && l2 != null){

if(l1.val < l2.val){

tail.next = l1;

l1 = l1.next;

}else {

tail.next = l2;

l2 = l2.next;

}

tail = tail.next;

}

if(l1 != null){

tail.next = l1;

}else{

tail.next = l2;

}

return res.next;

}

// 原地归并,并不申请新的数组空间,算法实现上,其实是找规律。

public ListNode mergeKLists(ListNode[] lists) {

// 步长为 2 时,和后面的第 1 个合并

// 步长为 4 时,和后面的第 2 个合并

// ...

if(lists == null){

return null;

}

int len = lists.length;

int interval = 1;

while(interval < len){

for(int i = 0; i+interval < len; i += 2*interval){

lists[i] = merge(lists[i], lists[i + interval]);

}

interval *= 2;

}

return len != 0 ? lists[0] : null;

}

}

思路解析

对于解法一:

由于我们很熟悉两个有序链表的合并,每次两两比较,将较小值合并进最终的有序链表。问题推广到 k 个有序链表的合并,我们自然而然的可以想到,每次比较 k 个值,将其中最小的合并进最终的有序链表。所以我们可以使用一个优先队列(小根堆)来维护 k 个结点的大小信息,而不需要每次从头开始比较 k 个结点的大小。

对于解法二:

k 个有序链表合并这个问题,可以看作是归并排序回溯过程中的一个状态,使用分治的思想求解,不过和归并排序不同的是,这里只有治而没有分。

下面这个图详细体现了算法过程,并且我们可以原地归并,不需要申请新的数组空间:

9e080d9c40fa

image.png

原地归并的算法实现,其实就是一个找规律问题。第一轮的归并是,0 和 1,2 和 3,4 和 5 ...;第二轮的归并是,0 和 2,4 和 6 ...;第三轮的归并是,0 和 4,4 和 8 ...;... 直到两归并链表的间距大于等于数组长度为止。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值