数据结构学习-LeetCode21.合并两个有序链表C语言

LeetCode21.合并两个有序链表
难度 简单
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

数据结构是编程的基础,它决定了一个程序员的发展潜力,所以练好数据结构还是很重要的,
最近我会先在力扣上将数据结构的题写一遍(先用C语言写),并将总结与感想记录在我的博客里。
(Ps:蒟蒻一枚,求大佬轻点喷)

对于这道题目,最开始我的做法是暴力破解,大概思路如下:

先分别找到list1与list2链表中的最小节点,然后比较将更小的那个节点添加到新的链表中去,用while循环这一过程直至有一个链表为NULL结束循环,
然后将两个链表中不为NULL的链表添加到新链表中去,最后返回新链表。

看完官方题解后发现这个方法属实笨(~ ̄(OO) ̄)ブ,又耗时间,又占内存,需要开好几个指针,
不过由于测试数据不稳定的原因,最终的测试结果一点波动,最好的情况耗时0ms,打败100%用户(我当时一脸懵逼,不应该呀,这个算法),最坏8ms,打败8%用户......
可能也许maybe时间复杂度为O(n3)?空间复杂度我也不会算,开了这么多指针估计也不会低,至少是O(n+m)吧....

代码如下:
我先定义了这么多指针,,,看着就令人头皮发麻

    struct ListNode* newList;   //新链表
    struct ListNode* headL1;    //list1链表指针
    struct ListNode* headL2;    //list2链表指针
    struct ListNode* headNew;   //新链表指针
    struct ListNode* newNode;   //添加的新节点
    struct ListNode* ptrA;  //查找list1中最小值的指针
    struct ListNode* ptrB;  //查找list2中最小值的指针

然后进行初始化设置:

    newList = (ListNode*)malloc(sizeof(ListNode));
    headL1 = l1->next;
    headL2 = l2->next;
    headNew = newList;

接着就开始在while循环里面依次查找最小值,然后比较,把更小的值添加到新链表中去:

while (headL1 != NULL && headL2 != NULL){
    //1.分别找到两个链表中的最小值 
    int minA = headL1->val;
    int minB = headL2->val;
    ptrA = headL1;
    ptrB = headL2;
    //循环链表1,找到最小值
    while (ptrA != NULL){
	if (minA > ptrA->val) minA = ptrA->val;
        ptrA = ptrA->next;
    }
    //循环链表2,找到最小值
    while (ptrB != NULL){
        if (minB > ptrB->val) minB = ptrB->val;
	ptrB = ptrB->next;	
    }
    //2.找出两个最小值中的最小值,并将其赋给新的结点 
    newNode = (ListNode*)malloc(sizeof(ListNode));
    if (minA < minB){
        newNode->val = minA;
        //如果最小值在list1中,就将list1的指针往后移
        headL1 = headL1->next;
    }else{
	newNode->val = minB;
        //如果最小值在list2中,就将list2的指针往后移
	headL2 = headL2->next;
    }
    //将节点赋给新链表 
    headNew->next = newNode;
    //让新链表的指针往后移
    headNew = newNode;
}

好吧,我知道很蠢,求喷轻点QAQ
接下来就是把两个链表中比较长的那一个链表未遍历完的节点(已排好序的)添加到新链表上去
这是我自己想出来的做法:

	//如果第一个链表为空了,将第二个链表剩余的添加到新链表 
	if (headL1 == NULL && headL2 != NULL){
		ptrB = headL2;
		while (ptrB != NULL){
			newNode = (ListNode*)malloc(sizeof(ListNode));
			newNode->val = ptrB->val;
			headNew->next = newNode;
			headNew = newNode;
			ptrB = ptrB->next;
		}
	}
	//如果第二个链表为空了,将第一个链表剩余的添加到新链表 
	if (headL1 != NULL && headL2 == NULL){
		ptrA = headL1;
		while (ptrA != NULL){
			newNode = (ListNode*)malloc(sizeof(ListNode));
			newNode->val = ptrA->val;
			headNew->next = newNode;
			headNew = newNode;
			ptrA = ptrA->next;
		}
	}

这是我看了官方题解后发做法:

    headNew->next = headL1 == NULL ? headL2 : headL1;

接着返回newList->next就可以了。。。
让我们快点跳过这个羞耻的做法,去看看官解吧QAQ

解法一:递归 时间复杂度为O(n+m) 空间复杂度为O(n+m)

这个已经可以把我的解法按在地上摩擦了...
先上代码再讲解:

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
    if (l1 == NULL){
        return l2;
    }else if (l2 == NULL){
	return l1;
    }else if (l1->val < l2->val){
	l1->next = mergeTwoLists(l1->next, l2);
	return l1;
    }else{
        l2->next = mergeTwoLists(l1, l2->next);
        return l2;
    }
}

首先,要用递归就得有出口,不给函数提供出口的话就会一直死循环的执行下去

我们的出口就是当两个链表其中一个遍历完后(即指针指向为NULL)就退出循环,并将另一个链表返回(也就是把剩下的数接到最后一个指针上面去)
在这个解法上我们不用新开链表,只需要假象存在一个新的链表,然后list1与list2分别为两个链表的指针,
让指针指向的元素比较大小,较小的元素指针后移,下一次递归里我们再比较当前两个指针指向元素的大小

如图有两个链表listA与liistB,递归时我们先比较A1与B1大小,1 < 2,
所以listA指针后移,listB指针不变
文字太抽象,直接上图

(gif图片大小咋调整哟QAQ)
最终递归结束后将节点从6依次返回

解法二:迭代

代码先走起

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
    struct ListNode* preHead;
    preHead = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* prev;
    prev = preHead;
    while(l1 != NULL && l2 != NULL){
	if (l1->val <= l2->val){
	    prev->next = l1;
   	    l1 = l1->next;
	}else{
	    prev->next = l2;
	    l2 = l2->next;
	}
	prev = prev->next;
    }
    prev->next = l1 == NULL ? l2 : l1; 
    return preHead->next;
}

这里我们需要创建两个指针变量,一个为头指针preHead方便之后返回最终链表,另一个为prev用来指向当前元素的(即定位功能,类似于方向标)(注意,这里了l1和l2也是指向元素的作用)
同样的,循环的终止条件为两个链表中有其中一个被遍历完就终止循环
在循环中比较l1和l2指向的元素大小,让prev指针指向其中较小元素的节点,依此循环下去,循环结束后依然把剩余的节点接到prev后面。
法二可以直接去看力扣官方题解的动图,讲的很详细。
按我的理解,这里的preHead就像一个无家可归的孩子,而prev就是给他指向回家路的好心人,他要判别哪条路才能让preHead回到家,这样他就不得不比较每个元素的大小,选取较小的元素作为下一条正确的回家路,最终prev将所有路连起来,然后我们返回第一条路(即preHead->next),让这位孩纸按着链表的路走回家。

如果有不好的地方欢迎大家在评论区讨论呀

(当然这题比较简单,以后我会持续更新学习数据结构的记录的)

posted @ 2020-12-16 19:18  余野AE  阅读(425)  评论(0编辑  收藏  举报