首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >《链表面试基础看点:这里不止“快慢指针”的完美实现,更懂“哨兵节点”的巧妙运用》

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

作者头像
晨非辰Tong
发布2025-12-23 15:06:50
发布2025-12-23 15:06:50
20
举报
文章被收录于专栏:C++C++

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

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

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

代码语言:javascript
复制
/**
 * 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)(“哨兵”位)

  • 方法一:创建新链表,比较大小,小的尾插新链表
代码语言:javascript
复制
/**
 * 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;
}

--前面进行的单独判断为空:防止后续解引用空指针。 --最后面的是判断哪个链表走到了空,直接将另一个链表链接到尾节点。

  • 改良:减少代码冗余

--发现上面的两串代码重复性很高,为什么?? :初始时,设置的头节点、尾节点为空,导致尾插都要判断是否为空——>创建非空链表?——>可以!

代码语言:javascript
复制
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 的下一个节点(不要倒在小错误上!)。


回顾: 《一招吃透链表操作:三指针反转法,面试遇到链表题再也不慌!》 《算法面试“必杀技”:双指针法高效解决数组原地操作》

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-10-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 876. 链表的中间结点 - 力扣(LeetCode)(快慢指针)
  • 2. 21. 合并两个有序链表 - 力扣(LeetCode)(“哨兵”位)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档