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

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

🔥@晨非辰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

一文搞懂Webhook:原理、实操与Langflow落地场景

一文搞懂Webhook:原理、实操与Langflow落地场景

Webhook作为现代系统集成的核心轻量通信机制,以“事件驱动”模式实现跨应用实时数据同步,解决了传统API轮询效率低、资源浪费的痛点。本文从定义、工作原理、核心优势、安全实践四个维度拆解Webhook,重点讲解Langflow产品中Webhook组件的实用操作,并结合企业协作、供应链管理、客户服务等实际场景,展示其如何快速实现无代码/低代码的自动化工作流,帮助开发者与业务人员高效落地跨系统联动需求。 一、什么是Webhook?核心认知拆解 简单来说,Webhook是一种基于HTTP回调的被动式通信机制,本质是“事件触发+自动推送”的桥梁——当系统A发生特定事件(如订单支付、代码提交、消息发送)时,会主动向系统B预先配置的URL(回调地址)发送请求,将事件数据实时推送至系统B,无需系统B主动查询(即轮询)。 类比生活场景:传统API轮询像你反复刷新快递物流页面查进度,而Webhook像快递员送货上门时主动打电话通知你,高效且无需额外操作。其核心价值在于“实时性”与“低资源消耗”,这也是它被广泛应用于现代应用集成的关键原因。 1.1 核心工作流程(4步闭环)

By Ne0inhk

Web 服务与 I/O 模型

一、Web 服务介绍 1.1.1 Apache prefork 模型(预派生模式) * 核心机制:主控制进程派生多个独立子进程,使用select模型,最大并发 1024;每个子进程单线程响应用户请求 * 资源特性:占用内存较多,但稳定性极高 * 配置特点:可设置进程数的最大值和最小值 * 适用场景:访问量中等的场景 * 优缺点 * ✅ 优点:极致稳定,故障隔离性好 * ❌ 缺点:每个请求对应一个进程,资源占用高,并发能力弱,不适合高并发场景 1.1.2 Apache worker 模型(多进程 + 多线程混合模式) * 核心机制:主进程启动多个子进程,每个子进程包含固定线程数;线程处理请求,线程不足时新建子进程补充 * 资源特性:相比 prefork 内存占用更少,支持更高并发

By Ne0inhk

算法刷题--长度最小的子数组

文章目录 * 1、209 长度最小的子数组 * 1. 观察切入点:为什么要用滑动窗口? * 2. 核心原理:滑动窗口(毛毛虫算法) * 3. 思路推演:逻辑如何一步步清晰? * 4. 避坑指南:这里的陷阱在哪里? * 2、3 无重复字符的最长子串 * 3、 713 乘积小于K的子数组 * 1. 核心原理:乘法与加法的“起点”不同 * 2. 观察切入点:这行代码的“计数逻辑” * 3. 思路深度拆解:为什么要有 `if(k <= 1) return 0;`? * 4. 避坑指南:数据溢出 * 总结 * 4 904 水果成蓝 * 1. 观察切入点:

By Ne0inhk