21 合并两个有序链表
题源:力扣21
将两个升序链表合并为一个新的升序链表并返回,新链表是通过拼接给定的两个链表的所有节点组成的。
解决思路与数组一样,一般有两种。一种是新建一个链表,然后分别遍历两个链表,每次都选最小的结点接到新链表上,最后排完。另外一个就是将一个链表结点拆下来,逐个合并到另外一个对应位置上去。这个过程本身就是链表插入和删除操作的拓展,难度不算大,这时候代码是否优美就比较重要了。
方法一:递归
- 判断两个链表是否有一个为空,如果有,则返回另一个链表。
- 比较两个链表当前节点的值,将较小的节点作为新链表的头节点。
- 将较小节点的下一个节点和较大节点递归地进行合并,得到的链表连接到较小节点后面。
- 返回新链表的头部。
以下是cpp代码:
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == nullptr) {
return l2;
} else if (l2 == nullptr) {
return l1;
} else if (l1->val < l2->val) {
l1->next = mergeTwoLists(l1->next, l2);
return l1;
} else {
l2->next = mergeTwoLists(l1, l2->next);
return l2;
}
}
};
以下是Java代码:
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
} else if (l2 == null) {
return l1;
} else if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
}
Java和cpp在这个程序中的两个区别就是Java用null,cpp用nullptr;Java用l1.val,cpp用l1->val,即指针表示方法有些不同
为什么需要考虑边界情况呢?这样做是为了确保我们的代码在处理不同情形时是安全可靠的。在这个函数中,边界情况指的是当两个链表中至少有一个为空时:l1为空或者l2为空。在这种情况下,我们只需要返回另一个链表即可,因为没有多余的节点可以合并。这样的判断有利于处理特殊情况,确保代码的正常执行。
方法二:迭代(新建一个链表)(循环+双链表)
- 判断两个链表是否有一个为空,如果有,则返回另一个链表。
- 比较两个链表当前节点的值,将较小的节点作为新链表的头节点。
- 将较小节点的下一个节点和较大节点递归地进行合并,得到的链表连接到较小节点后面。
- 返回新链表的头部。
从头开始比较两个链表的节点值,将较小的节点添加到新链表中,然后移动指针继续比较,直到其中一个链表遍历完,将剩余的节点直接连接到新链表的末尾。
以下是Java代码:
public ListNode mergeTwoLists (ListNode list1, ListNode list2) {
ListNode newHead=new ListNode(-1);
ListNode res=newHead;
while(list1!=null||list2!=null){
//情况1:都不为空的情况
if(list1!=null&&list2!=null){
if(list1.val<list2.val){
newHead.next=list1;
list1=list1.next;
}else if(list1.val>list2.val){
newHead.next=list2;
list2=list2.next;
}else{ //相等的情况,分别接两个链
newHead.next=list2;
list2=list2.next;
newHead=newHead.next;
newHead.next=list1;
list1=list1.next;
}
newHead=newHead.next;
//情况2:假如还有链表一个不为空
}else if(list1!=null&&list2==null){
newHead.next=list1;
list1=list1.next;
newHead=newHead.next;
}else if(list1==null&&list2!=null){
newHead.next=list2;
list2=list2.next;
newHead=newHead.next;
}
}
return res.next;
}
代码优化:
优化一:第一个大while里有三种情况,我们可以将其合并成两个
优化二:后面两个小的while循环
以下是Java代码:
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode prehead = new ListNode(-1);
ListNode prev = prehead;
while (list1 != null && list2 != null) {
if (list1.val <= list2.val) {
prev.next = list1;
list1 = list1.next;
} else {
prev.next = list2;
list2 = list2.next;
}
prev = prev.next;
}
// 最多只有一个还未被合并完,直接接上去就行了,这是链表合并比数组合并方便的地方
prev.next = list1 == null ? list2 : list1;
return prehead.next;
}
以下是cpp代码:
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* preHead = new ListNode(-1);
ListNode* prev = preHead;
while (l1 != nullptr && l2 != nullptr) {
if (l1->val < l2->val) {
prev->next = l1;
l1 = l1->next;
} else {
prev->next = l2;
l2 = l2->next;
}
prev = prev->next;
}
// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
prev->next = l1 == nullptr ? l2 : l1;
return preHead->next;
}
};
方法三:优先队列
- 创建一个优先队列,将两个链表的所有节点依次加入队列,队列会按节点的值进行排序。
- 创建一个新的链表,并从队列中逐个弹出节点,将弹出的节点加入到新链表的尾部。
- 返回新链表的头部。
【笔者还没学到优先队列,就不列代码了】
总结
对于将两个升序链表合并的问题,以下是三种常见的解决方法:
方法一:迭代法
- 初始化一个新的链表,并设立一个指针指向新链表的头部。
- 比较两个链表当前节点的值,将较小的节点添加到新链表中,并将指针移到下一个位置。
- 重复步骤2,直到其中一个链表遍历完。
- 将剩下的链表连接到新链表的后面。
- 返回新链表的头部。
方法二:递归法
- 判断两个链表是否有一个为空,如果有,则返回另一个链表。
- 比较两个链表当前节点的值,将较小的节点作为新链表的头节点。
- 将较小节点的下一个节点和较大节点递归地进行合并,得到的链表连接到较小节点后面。
- 返回新链表的头部。
方法三:优先队列
- 创建一个优先队列,将两个链表的所有节点依次加入队列,队列会按节点的值进行排序。
- 创建一个新的链表,并从队列中逐个弹出节点,将弹出的节点加入到新链表的尾部。
- 返回新链表的头部。