先来看下题目描述:
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = []
输出:[]
示例 3:
输入:l1 = [], l2 = [0]
输出:[0]
提示:
两个链表的节点数目范围是 [0, 50]
-100 <= Node.val <= 100
l1 和 l2 均按 非递减顺序 排列
本文采用思维的递进和代码的逐步优化来解析这道题
本题虽然不复杂,但是很多题目的基础,解决思路与数组一样,一般有两种。一种是新建一个链表,然后分别遍历两个链表,每次都选最小的结点接到新链表上,最后排完。另外一个就是将一个链表结点拆下来,逐个合并到另外一个对应位置上去。这个过程本身就是链表插入和删除操作的拓展,难度不算大,这时候代码是否优美就比较重要了。先看下面这种:
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
// 建立新的链表头结点
ListNode* newHead = (ListNode*)malloc(sizeof(ListNode));
newHead->val = -1;
newHead->next = NULL;
// ans用于记录返回值
ListNode* ans = newHead;
// 同时遍历两个链表,类似双指针
while(list1 != NULL || list2 != NULL) {
// case1.两个链表都不空
if(list1 != NULL && list2 != NULL) {
// 比较两个链表当前节点的值,把小的那个接到newHead后面
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 = list1;
list1 = list1->next;
newHead = newHead->next;
newHead->next = list2;
list2 = list2->next;
}
newHead = newHead->next;
} else if(list1 != NULL && list2 == NULL) {
// case2.List2已经遍历完了,把list1剩余元素全部接到后面去
newHead->next = list1;
list1 = list1->next;
newHead = newHead->next;
} else if(list2 != NULL && list1 == NULL) {
// case3.list1同理
newHead->next = list2;
list2 = list2->next;
newHead = newHead->next;
}
}
return ans->next;
}
};
看到这段代码,你会给他打多少分呢
这段代码明显非常臃肿,因为所有的处理都放在一个大循环里,我们是不是可以把某个或某几个处理拉出来呢,也就是这样:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* newHead = (ListNode*)malloc(sizeof(ListNode));
newHead->val = -1;
newHead->next = NULL;
ListNode* ans = newHead;
while(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 = list1;
list1 = list1->next;
newHead = newHead->next;
newHead->next = list2;
list2 = list2->next;
}
newHead = newHead->next;
}
while(list1 != NULL) {
newHead->next = list1;
list1 = list1->next;
newHead = newHead->next;
}
while (list2 != NULL) {
newHead->next = list2;
list2 = list2->next;
newHead = newHead->next;
}
return ans->next;
}
肉眼可见的瘦身哈,其实只不过是把上面的大while里面处理的三种case分成三个while进行处理,代码是变好看了,可是耗时更多了,我们来进一步优化:
我们看后面两个小的while循环,都是把一个链表拼到另一个链表的后面,而链表拼接只需要把头结点拼好,后面的是不是自然而然就跟着接上去了啊,因此这两个循环可以直接简化到一行代码:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* newHead = (ListNode*)malloc(sizeof(ListNode));
newHead->val = -1;
newHead->next = NULL;
ListNode* ans = newHead;
while(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 = list1;
list1 = list1->next;
newHead = newHead->next;
newHead->next = list2;
list2 = list2->next;
}
newHead = newHead->next;
}
newHead->next = list1 == NULL ? list2 : list1;
return ans->next;
}
用的是三目运算符,尾巴是不是非常简洁了,但是这样一对比显得肚子部分还是很臃肿啊,那么大一个while,我们接着优化:
我们重新审视一下while循环里面的逻辑,上面的想法是分三种情况:<, >, ==。考虑一下把相等的这种情况跟前面合并起来,于是就可以这样写:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* newHead = (ListNode*)malloc(sizeof(ListNode));
ListNode* ans = newHead;
while(list1 != NULL && list2 != NULL) {
if(list1->val <= list2->val) {
newHead->next = list1;
list1 = list1->next;
} else {
newHead->next = list2;
list2 = list2->next;
}
newHead = newHead->next;
}
newHead->next = list1 == NULL ? list2 : list1;
return ans->next;
}
这种方式明显更高级,但是考虑的方面也很多,一开始想确实不一定就能想到这一步,但是通过思维递进和代码逐步优化,一样能写出优雅的代码
合并K个链表
会了合并两个链表,那合并K个链表就迎刃而解了,直接亮亮合并到最后,直接上代码:
struct ListNode* mergeKLists(struct ListNode* lists[], int size) {
struct ListNode* res = NULL;
for (int i = 0; i < size; i++) {
res = mergeTwoLists(res, lists[i]);
}
return res;
}