本篇博客计划讲解力扣“21. 合并两个有序链表”这道题,这是题目链接。
老规矩,先来审下题干。
输出示例如下:
提示:
这道题目相当经典,同时是校招的常客。大家先思考一下,再来听我讲解。
思路:类似归并排序,把2个有序链表合并成一个新的有序链表,第一反应应该是:每次取小的结点,尾插到新的链表中。
首先定义一个新的链表,这里我定义一个哨兵位的头结点:
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
// 哨兵位的头结点
struct ListNode* head = (struct ListNode*)malloc(sizeof(struct ListNode));
head->val = 0;
head->next = NULL;
}
由于后面需要反复把小的结点拿出来尾插,单链表的尾插非常麻烦,需要先遍历链表找到尾结点。但是有个简单的方法,定义一个指针,每次记录新的尾结点,省掉找尾的过程。
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
// 哨兵位的头结点
struct ListNode* head = (struct ListNode*)malloc(sizeof(struct ListNode));
head->val = 0;
head->next = NULL;
// 记录尾结点,方便尾插
struct ListNode* tail = head;
}
下面我们用2个指针,分别遍历2个链表。注意循环条件是用“并且”链接的,因为其中一个指针走到NULL就结束了。
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
// 哨兵位的头结点
struct ListNode* head = (struct ListNode*)malloc(sizeof(struct ListNode));
head->val = 0;
head->next = NULL;
// 记录尾结点,方便尾插
struct ListNode* tail = head;
// 遍历2个链表,取小的结点尾插
struct ListNode* cur1 = list1;
struct ListNode* cur2 = list2;
while (cur1 && cur2)
{
}
}
每次应该比较2个结点中的数据,取小的尾插到新链表中。
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
// 哨兵位的头结点
struct ListNode* head = (struct ListNode*)malloc(sizeof(struct ListNode));
head->val = 0;
head->next = NULL;
// 记录尾结点,方便尾插
struct ListNode* tail = head;
// 遍历2个链表,取小的结点尾插
struct ListNode* cur1 = list1;
struct ListNode* cur2 = list2;
while (cur1 && cur2)
{
if (cur1->val < cur2->val)
{
// 尾插cur1
}
else
{
// 尾插cur2
}
}
}
如何尾插呢?直接链接到tail的后面,尾插的结点成为新的tail。最后别忘了迭代,也就是让cur1或者cur2向后走一步,遍历下一个结点。
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
// 哨兵位的头结点
struct ListNode* head = (struct ListNode*)malloc(sizeof(struct ListNode));
head->val = 0;
head->next = NULL;
// 记录尾结点,方便尾插
struct ListNode* tail = head;
// 遍历2个链表,取小的结点尾插
struct ListNode* cur1 = list1;
struct ListNode* cur2 = list2;
while (cur1 && cur2)
{
if (cur1->val < cur2->val)
{
// 尾插cur1
tail->next = cur1;
tail = cur1;
cur1 = cur1->next;
}
else
{
// 尾插cur2
tail->next = cur2;
tail = cur2;
cur2 = cur2->next;
}
}
}
当while循环结束时,有一个链表已经遍历完了,另外一个链表还没有遍历完。只需要把没有遍历完的链表链接到tail后面即可。
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
// 哨兵位的头结点
struct ListNode* head = (struct ListNode*)malloc(sizeof(struct ListNode));
head->val = 0;
head->next = NULL;
// 记录尾结点,方便尾插
struct ListNode* tail = head;
// 遍历2个链表,取小的结点尾插
struct ListNode* cur1 = list1;
struct ListNode* cur2 = list2;
while (cur1 && cur2)
{
if (cur1->val < cur2->val)
{
// 尾插cur1
tail->next = cur1;
tail = cur1;
cur1 = cur1->next;
}
else
{
// 尾插cur2
tail->next = cur2;
tail = cur2;
cur2 = cur2->next;
}
}
// 链接还没有遍历完的链表
if (cur1)
{
tail->next = cur1;
}
else
{
tail->next = cur2;
}
}
由于最后链接的链表的结尾一定是NULL,就不用考虑把尾置空的问题了。
下面考虑返回的问题,应该返回head->next,因为哨兵位并不存储有效数据。但在这之前,需要把哨兵位释放了。
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
// 哨兵位的头结点
struct ListNode* head = (struct ListNode*)malloc(sizeof(struct ListNode));
head->val = 0;
head->next = NULL;
// 记录尾结点,方便尾插
struct ListNode* tail = head;
// 遍历2个链表,取小的结点尾插
struct ListNode* cur1 = list1;
struct ListNode* cur2 = list2;
while (cur1 && cur2)
{
if (cur1->val < cur2->val)
{
// 尾插cur1
tail->next = cur1;
tail = cur1;
cur1 = cur1->next;
}
else
{
// 尾插cur2
tail->next = cur2;
tail = cur2;
cur2 = cur2->next;
}
}
// 链接还没有遍历完的链表
if (cur1)
{
tail->next = cur1;
}
else
{
tail->next = cur2;
}
// 释放哨兵位
struct ListNode* del = head;
head = head->next;
free(del);
del = NULL;
return head;
}
这样就过了。是不是很简单?
总结
- 合并有序链表,即“归并排序”的思路:取小的尾插到新链表。
- 使用“哨兵位的头结点”,可以避免复杂的分类讨论。
- 使用tail记录尾结点,是一个常见的尾插思路。
- 最后别忘了释放哨兵位。
感谢大家的阅读!