【C】链表算法题4 -- 合并两个有序链表
leetcode链接https://leetcode.cn/problems/merge-two-sorted-lists/description/https://leetcode.cn/problems/merge-two-sorted-lists/description/https://leetcode.cn/problems/merge-two-sorted-lists/description/
我们先来看一下题目描述:
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例2:
输入:l1 = [], l2 = []
输出:[]
函数头部:
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
}
很显然,这个题目是给你两个有序链表的首节点,让你返回合并之后链表的首节点。
思路
需要注意的是合并的两个链表是升序链表,也就是两个链表节点中的值是从小到大依次排列的。
这个题的思路我们可以借鉴之前第一道链表算法题 -- 移除链表元素的思想,新建一个链表用来链接原来两个链表里面的较小的节点。同样,为了保存返回的节点的地址以及降低时间复杂度,所以这里仍选择创建两个指针变量 PHead 和 PTail 分别用来指向新链表的首节点和尾节点,再创建两个指针变量 l1 和 l2 分别指向 list1 和 list2 链表的首节点,然后让 l1 和 l2 分别遍历原来两个链表,每遍历一次就比较 l1 和 l2 指向节点 val 值的大小,然后让 val 值较小的尾插到新链表尾部,然后依次比较,直到其中一个链表遍历完成,循环就结束。
但是题目要求把两个链表所有的节点都合并为一个新链表,但是只有一个链表遍历完,节点都尾插到新链表里面,不代表着两个链表的节点都尾插到了新链表里面,如示例1,当 l2 遍历完list2走到NULL时:
list1中还有一个节点并未插入到新链表中,所以还需要一个循环,用来将 list1 剩下的节点依次尾插到新链表中;类似的,也有可能出现 list1 的节点全都尾插到新链表中, 而 list2 还有剩余的节点,所以也需要一个循环依次把 list2 的节点尾插到新链表中。
这段逻辑的代码为:
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
//创建新链表
ListNode* PHead, *PTail;
PHead = PTail = NULL;
ListNode* l1 = list1, * l2 = list2;
//将list1和list2中val值较小的节点尾差到新链表中
while (l1 && l2)
{
if (l1->val < l2->val)
{
//判断新链表是否为空
if (PHead == NULL)
{
PHead = PTail = l1;
l1 = l1->next;
}
else
{
PTail->next = l1;
l1 = l1->next;
PTail = PTail->next;
}
}
else
{
//判断链表是否为空
if (PHead == NULL)
{
PHead = PTail = l2;
l2 = l2->next;
}
else
{
PTail->next = l2;
l2 = l2->next;
PTail = PTail->next;
}
}
}
//将list1中还未插入的节点依次尾插到新链表中
while (l1)
{
PTail->next = l1;
l1 = l1->next;
PTail = PTail->next;
}
//将list2中还未插入的节点依次尾插到新链表中
while (l2)
{
PTail->next = l2;
l2 = l2->next;
PTail = PTail->next;
}
return PHead;
}
这里还需要考虑特殊情况,就是 list1 或者 list2 为空的情况,在这种情况下第一个循环是进不去的,而如果 list1 为空,list2 不为空,那么就会进入第二个循环,会发生对 patil ,也就是NULL指针的解引用,会发生错误;同样,如果 list1 不为空,而 list2 为空,也会发生同样的情况。所以这里也需要特殊处理一下,如果 list1 为空,那就返回list2,如果 list2 为空,那就返回list1。完整代码如下:
代码
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
//判断两个链表为不为空
if (list1 == NULL)
{
return list2;
}
if (list2 == NULL)
{
return list1;
}
//创建新链表
ListNode* PHead, *PTail;
PHead = PTail = NULL;
ListNode* l1 = list1, * l2 = list2;
//将list1和list2中val值较小的节点尾差到新链表中
while (l1 && l2)
{
if (l1->val < l2->val)
{
//判断新链表是否为空
if (PHead == NULL)
{
PHead = PTail = l1;
l1 = l1->next;
}
else
{
PTail->next = l1;
l1 = l1->next;
PTail = PTail->next;
}
}
else
{
//判断链表是否为空
if (PHead == NULL)
{
PHead = PTail = l2;
l2 = l2->next;
}
else
{
PTail->next = l2;
l2 = l2->next;
PTail = PTail->next;
}
}
}
//将list1中还未插入的节点依次尾插到新链表中
while (l1)
{
PTail->next = l1;
l1 = l1->next;
PTail = PTail->next;
}
//将list2中还未插入的节点依次尾插到新链表中
while (l2)
{
PTail->next = l2;
l2 = l2->next;
PTail = PTail->next;
}
return PHead;
}
改进
写完上面那个代码我们可以发现代码特别的长,原因就是其中有一段代码相同,就是在第一个循环里面,要判断新链表为不为空(PHead == NULL),所以这里我们可以改进一下,就是动态开辟一个节点,让PHead 与 PTail 都指向它,这样就不必判断链表为不为空了,也就省去了那段代码。
如果这样改进的话,最后返回的是 PHead 指向节点的下一个节点的地址,需要在最后释放一下动态开辟的那个节点,当然不要忘记把PHead置为NULL,改进后的完整代码如下:
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
//list1为空的时候
if (list1 == NULL)
{
return list2;
}
//list2为空的时候
if (list2 == NULL)
{
return list1;
}
ListNode* l1 = list1, *l2 = list2;
//新建一个带头节点的新链表
ListNode* PHead,*PTail;
PHead = PTail = (ListNode*)malloc(sizeof(ListNode));
//开始尾插,谁小谁尾插
while (l1 && l2)
{
if (l1->val < l2->val)
{
PTail->next = l1;
l1 = l1->next;
PTail = PTail->next;
}
else
{
PTail->next = l2;
l2 = l2->next;
PTail = PTail->next;
}
}
//如果list1还有结点,尾插到链表中
while (l1)
{
PTail->next = l1;
l1 = l1->next;
PTail = PTail->next;
}
如果list2还有结点,尾插到链表中
while (l2)
{
PTail->next = l2;
l2 = l2->next;
PTail = PTail->next;
}
//实际返回的为新链表头节点的下一个结点
ListNode* retnode = PHead->next;
free(PHead);
PHead = NULL;
return retnode;
}