😈博客主页:🐼大家好我叫张同学🐼
💖 欢迎点赞 👍 收藏 💗留言 📝 欢迎讨论! 👀
🎵本文由 【大家好我叫张同学】 原创,首发于 CSDN 🌟🌟🌟
✨精品专栏(不定时更新) 【数据结构+算法】 【做题笔记】【C语言编程学习】
☀️ 精品文章推荐
【C语言进阶学习笔记】三、字符串函数详解(1)(爆肝吐血整理,建议收藏!!!)
【C语言基础学习笔记】+【C语言进阶学习笔记】总结篇(坚持才有收获!)
前言 |
为什么要写
刷题笔记
?
写博客
的过程也是对自己刷题过程的梳理
和总结
,是一种耗时
但有效
的方法。
当自己分享的博客帮助到他人时,又会给自己带来额外的快乐和幸福。
(刷题的快乐+博客的快乐,简直是奖励翻倍,快乐翻倍
有木有QAQ🙈)
题目内容 |
将两个升序链表合并为一个新的
升序
链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
暴力求解法 |
我们可以将list2选为合并后待返回的链表,然后将list1中的结点合并到list2中。
算法图解 |
函数实现 |
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
struct ListNode *cur1 = list1,*cur2 = list2;
//以list2作为最终合并后的链表,当list2为空表,直接返回list1
if(cur2 == NULL)
return list1;
while(cur1){
//先保存cur1的下个结点位置,避免丢失
struct ListNode* next1 = cur1->next,*next2 = cur2->next;
while(next2 && cur2->val < cur1->val && next2->val < cur1->val){
cur2 = next2;
next2 = cur2->next;
}
if(cur2->val > cur1->val){//头插
cur1->next = cur2;
list2 = cur1;
cur2 = list2;
cur1 = next1;
continue;
}
cur1->next = cur2->next;
cur2->next = cur1;
cur1 = next1;
}
return list2;
}
注意:在实际写代码的时候,我们会发现仅有一个next
用来保存cur1
的下一个结点是不够的。cur2
的下一个结点的信息也需要保存(假设
下一个结点不为空
,若为空直接cur1
结点插入cur2
后即可),因为在比较的时候,当cur2->val
小于cur1->val
且cur2->next->val
大于cur1->val
时(cur2->next->val
就是next2->val
),才能将list1
中cur1
指向的结点插入到list2
中。否则,cur2
需要向后移动,直到满足条件为主。
小技巧
:
很多OJ题的测试用例非常多,即便我们画图分析了,可能还有很多场景
没能想到。这个时候就需要借助OJ中错误的测试用例
来帮助我们完善
、调整
、优化
代码。
这道题目很多场景我都是通过失败的测试用例才发现的,比如说:
①list1
不为空,list2
为空,这时候需要直接返回list1,无需合并。
②list1
中的很多结点一开始就小于list2
中的结点,这个时候就不能仅用尾插或者中间插入实现,就需要头插~
整体而言,这种暴力求解
的方式写起来并不轻松
,有时候需要考虑的条件和场景可能比其他方式更多!!!
接下来我们将学习一种更简单、更易于实现的方式~
新建链表法 |
为了方便合并链表,我们可以新建一个链表
newlist
,然后每次从list1
、list2
中将val
值小的结点尾插
到newlist
中。
算法图解 |
函数实现 |
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
struct ListNode* newlist = NULL,*tail = newlist;
struct ListNode *cur1 = list1,*cur2 = list2;
if(list1 == NULL && list2 == NULL)
return NULL;
else if(list1 == NULL)
return list2;
else if(list2 == NULL)
return list1;
while(cur1 && cur2){
struct ListNode* next1 = cur1->next;
struct ListNode* next2 = cur2->next;
if(cur1->val <= cur2->val){
if(tail == NULL){//newlist为NULL单独处理
cur1->next = tail;
newlist = cur1;
tail = newlist;
}
else{
cur1->next = tail->next;
tail->next = cur1;
tail = tail->next;
}
cur1 = next1;
}
else{
if(tail == NULL){//newlist为NULL单独处理
cur2->next = tail;
newlist = cur2;
tail = newlist;
}
else{
cur2->next = tail->next;
tail->next = cur2;
tail = tail->next;
}
cur2 = next2;
}
}
if(cur1)
tail->next = cur1;
else
tail->next = cur2;
return newlist;
}
这种方式比暴力求解的方法好了很多,至少没有那么多场景(什么时候要头插,什么时候中间插入等等)需要考虑,但是仍然有优化的空间。我们可以看到当新建的链表newlist
为不带哨兵位guard
的链表时,每次都需要去考虑newlist == NULL
这个条件,真是烦死人啦!为此,我们对以上代码进行优化,创建一个带哨兵位(带头)的链表newlist
。
代码优化 |
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
//创建哨兵位头节点,不存放有效数据
struct ListNode* guard = (struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode* newlist = guard,*tail = newlist;
struct ListNode *cur1 = list1,*cur2 = list2;
if(list1 == NULL && list2 == NULL)
return NULL;
else if(list1 == NULL)
return list2;
else if(list2 == NULL)
return list1;
while(cur1 && cur2){
struct ListNode* next1 = cur1->next;
struct ListNode* next2 = cur2->next;
if(cur1->val <= cur2->val){
cur1->next = tail->next;
tail->next = cur1;
tail = tail->next;
cur1 = next1;
}
else{
cur2->next = tail->next;
tail->next = cur2;
tail = tail->next;
cur2 = next2;
}
}
if(cur1)
tail->next = cur1;
else
tail->next = cur2;
//释放哨兵位头节点
newlist = newlist->next;
free(guard);
return newlist;
}
原创不易,求点赞+关注+收藏~ |