题目描述
描述
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有结点组成的。
示例1
输入:1 -> 2 -> 4, 1 -> 3 - > 4 - > 5
输出:1 -> 1 -> 2 -> 3 -> 4 -> 4 -> 5
解题分析:
思路1:将第二个链表拼接到第一个链表上,再对链表排序。
链表如何排序?详见这一篇文章“【双向链表练习】 - 对链表进行插入排序”。
代码实现1:
struct ListNode* mergeTwoLists(struct ListNode* L1, struct ListNode* L2)
{
//链表传参时,判断是否为NULL,否则解引用NULL,程序会崩
if (L1 == NULL)
return L2;//L1为空,L2不一定为空,为空或不为空也是返回L2
if (L2 == NULL)
return L1;//L2为空,L1不一定为空,为空或不为空也是返回L1
//将L2链接到L1上
struct ListNode* cur = L2;
//找L2尾节点,链上L1
while(cur->next)
{
cur = cur->next;
}
cur->next = L1;
//链表排序 - 链表的插入排序
//...
//详见【双向链表练习】 - 对链表进行插入排序
}
思路2:取L1上的结点,在L2上合适的位置插入。
代码实现2:
struct ListNode* mergeTwoLists(struct ListNode* L1, struct ListNode* L2)
{
if (L1 == NULL)
return L2;//L1为空,L2不一定为空,为空或不为空也是返回L2
if (L2 == NULL)
return L1;//L2为空,L1不一定为空,为空或不为空也是返回L1
//1 -> 2 -> 4, 1 -> 3 - > 4 - > 5
//2 -> 4, 1 ->1 -> 3 - > 4 - > 5
//4, 1 ->1 -> 2 -> 3 - > 4 - > 5
struct ListNode* nextL1 = NULL;
struct ListNode* curL1 = L1;
while (curL1)
{
struct ListNode* nextL2 = NULL;
struct ListNode* prevL2 = NULL;
struct ListNode* curL2 = L2;
nextL1 = curL1->next;
while (curL2)
{
nextL2 = curL2->next;
if (curL1->val <= curL2->val)
{
if (curL2 == L2)//头节点 - 头插
{
curL1->next = curL2;
curL2 = curL1;
L2 = curL2;
break;
}
else//其他地方的插入
{
prevL2->next = curL1;
curL1->next = curL2;
break;
}
}
prevL2 = curL2;
curL2 = nextL2;
}
curL1 = nextL1;
}
return L2;
}
思路3:从头开始,依次比较链表中的结点,每次取两个链表小的结点,尾插到新链表中。
1、无哨兵位的头结点
先取两个链表中,第一个节点小的结点作为头结点head,再迭代取数值小的结点先尾插,数值大的结点后尾插,
对于单链表的尾插,是先找到尾,再插入新的结点,取一个结点找一次尾,时间复杂度为O(N^2),效率很低,此时需要一个尾结点变量tail,
因此,这里使用tail保存尾节点,不需要找尾,方便直接链接到tail上。
代码实现3:
struct ListNode* mergeTwoLists(struct ListNode* L1, struct ListNode* L2)
{
//链表传参时,判断是否为NULL,否则解引用NULL,程序会崩
if (L1 == NULL)
return L2;//L1为空,L2不一定为空,为空或不为空也是返回L2
if (L2 == NULL)
return L1;//L2为空,L1不一定为空,为空或不为空也是返回L1
struct ListNode* head = NULL,*tail = NULL;//head用于返回,tail方便找到尾,并尾插
while(L1!=NULL && L2 !=NULL)
{
if(L1->val<L2->val)
{
//判断新链表头结点
if(tail == NULL)//用于第一次比较
{
head = tail = L1;
//L1 = L1->next;
}
else
{
//取小的尾插
tail->next = L1;
tail = tail->next;//更新尾指针,等价tail = L1;
//L1 = L1->next;
}
//将两个L1 = L1->next;放到这里
L1 = L1->next;
}
else
{
//判断新链表头结点
if(tail == NULL)//用于第一次比较
{
head = tail = L2;
//L2 = L2->next;
}
else
{
//取小的尾插
tail->next = L2;
tail = L2;//更新尾指针,等价tail = tail->next;
//L2 = L2->next;
}
//将两个L2 = L2->next;放到这里
L2 = L2->next;
}
}
//如果一个链表走完了,另一个链表不为空,直接将不为空的链表链接到尾上
if(L1 != NULL)
tail->next = L1;//如果l2为空链表,则tail空指针解引用
if(L2 != NULL)
tail->next = L2;//如果l1为空链表,则tail空指针解引用
return head;
}
优化1:上面是边比较边尾插,第一次比较特殊考虑判断头节点。先确定头节点,再比较尾插。
代码优化1:
struct ListNode* mergeTwoLists(struct ListNode* L1, struct ListNode* L2)
{
//链表传参时,判断是否为NULL,否则解引用NULL,程序会崩
if (L1 == NULL)
return L2;//L1为空,L2不一定为空,为空或不为空也是返回L2
if (L2 == NULL)
return L1;//L2为空,L1不一定为空,为空或不为空也是返回L1
struct ListNode* head = NULL,*tail = NULL;//head用于返回,tail方便找到尾,并尾插
//确定头结点
if (L1->val < L2->val)
{
head = tail = L1;
L1 = L1->next;
}
else
{
head = tail = L2;
L2 = L2->next;
}
//尾插链接
//确定头结点的时候,把它拿下来后,就应该往下走一次,再循环迭代取小的尾插
//取小的尾插,其中一个为空就结束循环,都不会空则继续
//判断条件,想的是结束的条件,写的是继续的条件
while (L1 != NULL && L2 != NULL)//while (L1 && L2)
{
if (L1->val < L2->val)
{
tail->next = L1;
//tail = L1;//等价tail = tail->next;
L1 = L1->next;
}
else
{
tail->next = L2;
//tail = L2;//等价tail = tail->next;
L2 = L2->next;
}
tail = tail->next;//相当于分别在L1 = L1->next;和L2 = L2->next;前面插入tail = L1和tail = L2.更新尾指针
}
if (L1 != NULL)
tail->next = L1;
else
tail->next = L2;
return head;
}
优化2:开辟带哨兵位的头结点指针(不存储有效数据),此时无需判断头结点,直接取小的结点尾插到哨兵为头节点的链表中。
注意返回头结点时,返回的是哨兵位下一个结点。
代码优化2:
struct ListNode* mergeTwoLists(struct ListNode* L1, struct ListNode* L2)
{
//链表传参时,判断是否为NULL,否则解引用NULL,程序会崩
if (L1 == NULL)
return L2;
if (L2 == NULL)
return L1;
struct ListNode* head = NULL, * tail = NULL;
//哨兵位结点,直接在哨兵位结点后面尾插,不需要确定头结点
head = tail = (struct ListNode*)malloc(sizeof(struct ListNode));//开辟哨兵位的头结点指针,不存有效数据
//虽然这里不初始化也可以,但是对tail初始化是个好习惯。
//尾插链接
while (L1 != NULL && L2 != NULL)//while (L1 && L2)
{
if (L1->val < L2->val)
{
tail->next = L1;
L1 = L1->next;
}
else
{
tail->next = L2;
L2 = L2->next;
}
tail = tail->next;
}
if (L1 != NULL)
tail->next = L1;
else
tail->next = L2;
struct ListNode* realHead = head->next;
free(head);
return realHead;//返回真正的头结点。如果直接head->next,则没有是否哨兵位结点。导致没有释放。
}