【单链表练习】 - 合并两个有序链表

题目描述

描述
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有结点组成的。
示例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,则没有是否哨兵位结点。导致没有释放。
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值