《链表面试基础看点:这里不止“快慢指针”的完美实现,更懂“哨兵节点”的巧妙运用》

《链表面试基础看点:这里不止“快慢指针”的完美实现,更懂“哨兵节点”的巧妙运用》

🔥@晨非辰Tong:个人主页 

👀专栏:《C语言》《数据结构与算法》《数据结构与算法刷题集》

💪学习阶段:C语言、数据结构与算法初学者

⏳“人理解迭代,神理解递归。”


引言:链表刷题进行时。寻找中间结点,看似简单,但你的解法是否考虑了所有边界情况?本文手把手带你用“快慢指针”写出完美解,以及“哨兵节点”对合并链表的简化实现。

目录

1.  876. 链表的中间结点 - 力扣(LeetCode)(快慢指针)

2.  21. 合并两个有序链表 - 力扣(LeetCode)


1.  876. 链表的中间结点 - 力扣(LeetCode)(快慢指针)

方法一:遍历链表计算总大小,算出mid,将首节点指针向后mid个节点。(容易想到)

方法二:使用快、慢指针,慢指针动一步、快指针动两步。

/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ typedef struct ListNode ListNode; struct ListNode* middleNode(struct ListNode* head) { //创建快慢指针 ListNode* fast, *slow; //首先都指向头节点 fast = head; slow = head; //循环条件将两种情况都包含 while(fast != NULL && fast->next != NULL)//条件换顺序? { //慢指针移动一节点 slow = slow->next; //快指针移动两个节点 fast = fast->next->next; } //返回slow return slow; }
--为什么慢指针移动一个节点,快指针移动两个节点,是固定的吗?
        :很好理解,因为是找 mid ,以慢指针所在节点为关注,那么快指针移动的是慢指针的两倍—>当快指针移动到尾节点或空,那么慢指针就指向 mid 。

--循环条件为什么不是或的关系?
        :在循环体中实现的的是奇数或偶数个节点链表的寻找,那么只要两个条件只有都满足才能进行。

    

--上面提到,循环条件可以换位置吗?

        :对偶数个节点进行分析


2.  21. 合并两个有序链表 - 力扣(LeetCode)(“哨兵”位)

  • 方法一:创建新链表,比较大小,小的尾插新链表
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ typedef struct ListNode ListNode; struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) { //判断两个链表是否是空链表 if(list1 == NULL) { return list2;//直接返回list2 } if(list2 ==NULL) { return list1;//直接返回list1 } //创建空链表:空的首节点 ListNode* newhead, *newtail; newhead = newtail = NULL; //两个指向指针 ListNode* l1 = list1; ListNode* l2 = list2; //遍历原链表 //先将相同长度内的节点进行比较 while(l1 != NULL && l2 != NULL) { if(l1->val < l2->val) { //l1尾插 //第一回尾插,链表为空 if(newhead == NULL) { newhead = newtail = l1;//为头、为尾 } //后续尾插 else { newtail->next = l1; newtail = newtail->next; //尾后移 } l1 = l1->next;//链表1后移 } else { //l2尾插 //第一回尾插,链表为空 if(newhead == NULL) { newhead = newtail = l2;//为头、为尾 } //后续尾插 else { newtail->next = l2; newtail = newtail->next; //尾后移 } l2 = l2->next; } //跳出循环,代表有链表走到了空 if(l1) { newtail->next = l1; } if(l2) { newtail->next = l2; } } return newhead; }
--前面进行的单独判断为空:防止后续解引用空指针。

--最后面的是判断哪个链表走到了空,直接将另一个链表链接到尾节点。
  • 改良:减少代码冗余
--发现上面的两串代码重复性很高,为什么??

        :初始时,设置的头节点、尾节点为空,导致尾插都要判断是否为空——>创建非空链表?——>可以!
typedef struct ListNode ListNode; struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) { //判断两个链表是否是空链表 if(list1 == NULL) { return list2;//直接返回list2 } if(list2 ==NULL) { return list1;//直接返回list1 } //创建非空链表:申请一份节点的空间 ListNode* newhead, *newtail; newhead = newtail = (ListNode*)malloc(sizeof(ListNode)); //两个指向指针 ListNode* l1 = list1; ListNode* l2 = list2; //遍历原链表 //先将相同长度内的节点进行比较 while(l1 != NULL && l2 != NULL) { if(l1->val < l2->val) { //l1尾插 newtail->next = l1; newtail = newtail->next; //尾后移 l1 = l1->next;//链表1后移 } else { //l2尾插 newtail->next = l2; newtail = newtail->next; //尾后移 l2 = l2->next; } //跳出循环,代表有链表走到了空 if(l1) { newtail->next = l1; } if(l2) { newtail->next = l2; } } //手动申请的空间自己要释放 //释放前,保存节点 ListNode* retnewhead = newhead->next; free(newhead); newhead = NULL; return retnewhead; }
改良:申请一份节点大小的空间,让头、尾节点直接指向这个空间(“哨兵节点”),就不需要后面的判断是否为空链表了。



--不是遇到重复代码,可以封装函数吗?

       
:没错,但只是代码的呈现形式改变了而已,实际的逻辑没有变。
--后面虽然结束后,会自动释放,但是还要保持良好的习惯。

--后面新创建的 retnewhead 指向的是 newhead 的下一个节点(不要倒在小错误上!)。

回顾:

《一招吃透链表操作:三指针反转法,面试遇到链表题再也不慌!》

《算法面试“必杀技”:双指针法高效解决数组原地操作》
结语:掌握“快慢指针”这一基础工具,就能优雅地解决“中间结点”问题,而“哨兵节点”的应用更为我们后续攻克“双向链表”等复杂题目打下了坚实基础下一题刷题之道,在于积跬步以致千里。,再见~~

Read more

【优选算法必刷100题】第027~28题(前缀和算法):寻找数组的中心下标、除自身以外数组的乘积

【优选算法必刷100题】第027~28题(前缀和算法):寻找数组的中心下标、除自身以外数组的乘积

🔥艾莉丝努力练剑:个人主页 ❄专栏传送门:《C语言》、《数据结构与算法》、C/C++干货分享&学习过程记录、Linux操作系统编程详解、笔试/面试常见算法:从基础到进阶 ⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平 🎬艾莉丝的简介: 🎬艾莉丝的算法专栏简介: 目录 027  寻找数组的中心下标  1.1  算法思路:前缀和 1.2  算法实现 1.2.1  C++实现 1.2.2  Java实现 1.3  博主手记 028  除自身以外数组的乘积 2.1  算法思路 2.2  算法实现

By Ne0inhk
【数据结构与算法】指针美学与链表思维:单链表核心操作全实现与深度精讲

【数据结构与算法】指针美学与链表思维:单链表核心操作全实现与深度精讲

🔥小龙报:个人主页 🎬作者简介:C++研发,嵌入式,机器人等方向学习者 ❄️个人专栏:《C语言》《【初阶】数据结构与算法》 ✨ 永远相信美好的事情即将发生 文章目录 * 前言 * 一、查找 * 二、指定位置之前或之后插入元素 * 2.1 在指定位置之前 * 2.2 在指定位置之后 * 三、指定位置删除或指定位置之后删除 * 3.1 在指定位置 * 3.2 指定位置之后 * 四、代码展现 * 4.1 SList.h * 4.2 SList.c * 4.3 test.c * 五、顺序表和链表的区别 * 总结与每日励志 前言

By Ne0inhk
【大数据存储与管理】分布式文件系统HDFS:07 HDFS编程实践

【大数据存储与管理】分布式文件系统HDFS:07 HDFS编程实践

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈大数据技术原理与应用 ⌋ ⌋ ⌋专栏系统介绍大数据的相关知识,分为大数据基础篇、大数据存储与管理篇、大数据处理与分析篇、大数据应用篇。内容包含大数据概述、大数据处理架构Hadoop、分布式文件系统HDFS、分布式数据库HBase、NoSQL数据库、云数据库、MapReduce、Hadoop再探讨、数据仓库Hive、Spark、流计算、Flink、图计算、数据可视化,以及大数据在互联网领域、生物医学领域的应用和大数据的其他应用。 【GitCode】专栏资源保存在我的GitCode仓库:https://gitcode.com/Morse_Chen/BigData_principle_application。 文章目录 * 一、HDFS常用命令 * 二、HDFS的Web页面 * 三、HDFS常用Java API及应用实例 * (一)常用Java API介绍 * (二)应用实例 * 总结

By Ne0inhk
详解数据结构之跳表

详解数据结构之跳表

目录 跳表的定义 跳表的演化过程 跳表的优化思路 跳表如何保证效率 跳表的时间复杂度 跳表的空间复杂度 跳表的查找 跳表的插入 跳表的删除 跳表的模拟实现 跳表与平衡搜索树及哈希表的对比 跳表的定义 跳表是由William Pugh(音译为威廉·普)发明的,最早出现于他在1990年发表的论文《Skip Lists: A Probabilistic Alternative to Balanced Trees》,跳表全称为跳跃列表,它允许快速查询,插入和删除一个有序连续元素的数据链表。 跳表的演化过程 对于单链表来说,即使数据是已经排好序的,想要查询其中的一个数据,只能从头开始遍历链表,这样效率很低,时间复杂度很高,是 O(n),如下图所示。 那我们有没有什么办法来提高查询的效率呢?我们可以为链表建立一个“索引”,这样查找起来就会更快,如下图所示,我们在原始链表的基础上,每两个结点提取一个结点建立索引,我们把抽取出来的结点叫作索引层或者索引,down

By Ne0inhk