
本篇博客计划讲解力扣“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记录尾结点,是一个常见的尾插思路。
 - 最后别忘了释放哨兵位。
 
感谢大家的阅读!



















