LeetCode21. 合并两个有序链表(难度:简单)
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
思路:
两个有序链表的合并的思想很简单,分别设置两个指针,从两个链表的第一个节点开始比较大小,将值小的节点放到新的链表中,依次进行,直到有一个链表为空,此时,再将剩下的部分连接到已生成的链表后面即可。直接上代码:
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
/*新建一个头结点以存放新生成的链表*/
ListNode l3 = new ListNode(0);
ListNode cur = l3;
/*当两个链表都不为空,则依次进行比较*/
while(l1!=null&&l2!=null){
if(l1.val<l2.val){
cur.next = l1;
l1 = l1.next;
}else{
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
/*有一条为空,则将剩下的链表连接到已生成的新链表后边*/
if(l1==null){
cur.next = l2;
}
if(l2==null){
cur.next = l1;
}
/*返回新链表的第一个节点*/
return l3.next;
}
}
LeetCode23. 合并K个有序链表(难度:困难)
合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
示例:
输入:
[
1->4->5,
1->3->4,
2->6
]
输出: 1->1->2->3->4->4->5->6
思路一:
其实做完两个有序链表的合并之后来做这题,总体思路是一致的,就是细节上会有所差异。我们需要每次从这K个链表中找到一个最小的(假设是从小到大),然后连接到新链表的尾部(尾插法),直至把K条链表上所有的节点都插入完成结束。
这里有一个点需要注意,没法像两个链表那样,在一条为空后,直接把另外一条链表插入到新链表的尾部,因为这里有K条,其实剩余的K-1条还需要继续进行比较。知道这个注意点之后,直接上代码:
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
/*新建一个头结点以存放新生成的链表*/
ListNode h = new ListNode(0);
ListNode cur = h;
/*在没有遍历完所有节点之前,一直循环*/
while(true){
ListNode minNode = null; // 最小值节点
int min = -1; //下标,用于标记是第几条链表的
for(int i=0;i<lists.length;i++){
if(lists[i]==null){
continue; //如果某一条链表已经被遍历完了,则跳过下面的操作
}
// 比较,找到最小的节点
if(minNode==null||lists[i].val<minNode.val){
minNode=lists[i];
min = i;
}
}
/*这个if语句很重要,是判断K条链表是否所有的节点都已经被遍历完,不加这条,则无法跳出循环*/
if(min==-1) {
break;
}
/*尾插法插入到新链表*/
cur.next = minNode;
cur = cur.next;
/*这一步也很重要,别忘了更新节点位置*/
lists[min] = lists[min].next;
}
return h.next;
}
}
时间复杂度:O(NK)
思路二:
既然每次都是要取一个最小值节点,这肯定会让我们想到一个数据结构,堆。我们可以使用小顶堆来对思路一的代码进行优化。在Java的库里边我们则可以使用优先队列PriorityQueue。如果有同学对优先队列不了解的,可以访问廖雪峰的Java基础教程。那么我们就对上述代码进行改进:
- 首先我们要创建一个优先队列,并且把K条链表的第一个节点存放到队列中。注意这里为了代码的简洁,使用了Java8新特性Lambda表达式.
Queue<ListNode> q = new PriorityQueue<ListNode>((a,b)-> a.val-b.val);
for(int i=0;i<lists.length;i++){
if(lists[i]!=null){
q.offer(lists[i]);
}
}
- 对循环的条件和内部进行改进
while(!q.isEmpty()){
/*每次从优先队列取出最小值节点*/
ListNode minNode = q.poll();
/*尾插法*/
cur.next = minNode;
cur = cur.next;
/*这一步的作用相当于思路一种的 更新指针操作,如果还有后续节点,则需要放入到优先队列中*/
if(minNode.next!=null){
q.offer(minNode.next);
}
}
完整代码:
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
ListNode h = new ListNode(0);
ListNode cur = h;
Queue<ListNode> q = new PriorityQueue<ListNode>((a,b)-> a.val-b.val);
for(int i=0;i<lists.length;i++){
if(lists[i]!=null){
q.offer(lists[i]);
}
}
while(!q.isEmpty()){
ListNode minNode = q.poll();
cur.next = minNode;
cur = cur.next;
if(minNode.next!=null){
q.offer(minNode.next);
}
}
return h.next;
}
}
时间复杂度:O(NLogK)
这是本人对有序链表合并的记录总结。看完这两道有序链表的题目,小伙伴们的思路有没有清晰一点呢?希望对你们有所帮助!