数据结构学习-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),让这位孩纸按着链表的路走回家。
如果有不好的地方欢迎大家在评论区讨论呀
(当然这题比较简单,以后我会持续更新学习数据结构的记录的)