【LeetCode/面试题】链表面试题

本文介绍了链表的几种常见操作,包括删除特定元素、翻转链表、找到链表中间节点、寻找倒数第k个节点、合并两个有序链表、分割链表以及判断链表的回文结构。每种操作都提供了两种不同的解题思路和对应的C++代码实现,适合学习和面试准备。
摘要由CSDN通过智能技术生成

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:Leetcode + 面试/笔试
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注


一、移除链表元素

1.1 题目描述

Leetcode链接:移除链表元素

在这里插入图片描述

1.2 思路一

  1. 创建两个值指针prevcur,一开始将prev置为NULLcur指向头结点
  2. 接着让prevcur往后遍历,如果cur指向的结点不等于val,则让prev指向curcur往后遍历。
  3. 如果cur指向的结点等于val,说明该结点就是要删除的结点,则先让prev指向的结点的next指向curnext,然后释放cur(删除节点),再让cur指向prev->next继续遍历。所以,prev指针的作用就是记录要删除的结点的前一个结点,使删除结点释放后再链接。
  4. 注意两个小细节:第一个,如样例二中,如果链表为空,直接返回NULL。第二个,如样例三中,如果链表内都是要删除的结点,就要单独处理(相当于头删)。(千万不能对空指针进行使用!)

【样例一动图展示】

在这里插入图片描述
【样例三动图展示】

在这里插入图片描述

1.3 思路一代码实现

struct ListNode* removeElements(struct ListNode* head, int val)
{
     if(head == NULL) 
     	return NULL;
     struct ListNode* cur = head;
     struct ListNode* prev = NULL;
     
     while (cur != NULL)
     {
        if (cur->val != val)
        {
            prev = cur;
            cur = cur->next;
        }
         else
         {
         	//单独处理样例三(头删)
            if (prev == NULL)
            {
                head = cur->next;
                free(cur);
                cur = head;
            }
            else
            {
                prev->next = cur->next;
                free(cur);
                cur = prev->next;
            }
        }
    }
    return head;
}

在这里插入图片描述

1.4 思路二 – 尾插

直接将不等于val的结点尾插到新链表,这就不用考虑样例三了。但是要注意,尾插后的tailnext要置为NULL,如样例一所示,如果不置为空,最后尾插5节点时,tailnext还指向6这个结点,而6这个结点最后被释放了,为了防止野指针问题,最后一定要把tail的next置为空

【动图展示】

在这里插入图片描述

1.5 思路二代码实现

struct ListNode* removeElements(struct ListNode* head, int val)
{
    struct ListNode* newnode = NULL;
    struct ListNode* cur = head; //遍历原链表
    struct ListNode* tail = NULL;//遍历新链表
    while (cur != NULL)
    {
        if (cur->val != val)
        {
        	//若一开始newnode为空,相当于一次赋值
            if(newnode == NULL)
            {
               newnode = tail = cur;
            }
            else
            {
            	//尾插
                tail->next = cur;
                tail = tail->next;
            }
            cur = cur->next;
        }
        else
        {
        	//如果是要释放的结点
        	//则记录释放结点的下一个结点
            struct ListNode* next = cur->next;
            free(cur);
            cur = next;
        }
        //防止野指针
        if (tail)
        {
            tail->next = NULL;
        }
    }
    return newnode;
}

在这里插入图片描述

二、翻转单链表

2.1 题目描述

Leetcode链接:翻转单链表

在这里插入图片描述

2.2 思路一 – 三指针

  1. 初始化三个指针prevcurnext,分别为NULL,指向头结点、指向curnext
  2. 接下来翻转链表,一开始让cur的next指向prev,然后再分别向后迭代三个指针
    【动图展示】
    在这里插入图片描述
    由动图最后可观察到,当cur指向NULL时,翻转结束,并且最后只用返回prev,即是翻转后的链表

2.3 思路一代码实现

struct ListNode* reverseList(struct ListNode* head)
{
    if (head == NULL) return NULL;
    struct ListNode* prev = NULL;
    struct ListNode* cur = head;
    struct ListNode* next1 = cur->next;
    while (cur)
    {
    	//翻转
        cur->next = prev;
		//迭代
        prev = cur;
        cur = next1;
        //当nex1走到空指针就没必要走了
        if (next1)
            next1 = next1->next;
    }
    return prev;
}

在这里插入图片描述

2.4 思路二 – 头插

头插到新链表

  1. 初始化指针cur指向头结点,并且新建一个指针newnode指向NULL
  2. cur的next指向的next指向newnode结点上,在每次头插前,要记录cur的下一个结点记为next,并且头插后要更新newnode,然后再让cur指向next
  3. 最后继续重复以上操作,直到cur指向NULL结束,返回newnode
    【动图展示】
    在这里插入图片描述

2.5 思路二代码实现

struct ListNode* reverseList(struct ListNode* head)
{
    struct ListNode* cur = head;
    struct ListNode* newnode = NULL;

    while (cur)
    {
    	//每次遍历前记录cur的下一个结点
        struct ListNode* next = cur->next;
        //头插
        cur->next = newnode;
        //更新newnode
        newnode = cur;
        cur = next;
    }
    return newnode;
}

在这里插入图片描述

三、链表的中间结点

3.1 题目描述

Leetcode链接:链表的中间结点

在这里插入图片描述

3.2 思路

这题用快慢指针就非常快了,定义两个指针fastslow,同时指向头结点,接下来让fast走两步,slow走一步。说到这里,就要分两种情况讨论:

  • 当结点个数为奇数时(如示例一)
    在这里插入图片描述
    由上动图观察可以得到,结点个数为奇数个时,当slow指向中间节点,fast的结束条件是fast->next = NULL
  • 当结点个数为偶数时(如示例二)
    *在这里插入图片描述由上动图观察可以得到,结点个数为偶数个时,当slow指向中间节点,fast的结束条件是fast = NULL

3.3 代码实现

struct ListNode* middleNode(struct ListNode* head)
{
    struct ListNode* fast,*slow;
    slow = head;
    fast = head;
    while (fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

在这里插入图片描述

四、链表中倒数第k个结点

Leetcode链接:链表中倒数第k个结点

4.1 题目描述

在这里插入图片描述

4.2 思路

这题同样也可以用快慢指针来做

  1. 定义两个指针fastslow,同时指向头结点
  2. 接着先让fastk
  3. 然后再让slowfast一起向后走一步
  4. 最后slow指向的结点就是链表中倒数第k个结点
    【动图演示】
    在这里插入图片描述
    从上动图可以发现,当fast指向空时,slow正好指向倒数第k个结点

4.3 代码实现

struct ListNode* getKthFromEnd(struct ListNode* head, int k)
{
    struct ListNode* fast = head;
    struct ListNode* slow = head;

    //先让fast走k步
    while (k --)
    {
        fast = fast->next;
    }

    //接着让fast和slow同时向后走1步
    while (fast)
    {
        fast = fast->next;
        slow = slow->next;
    }
    return slow;
}

在这里插入图片描述

面试官灵魂拷问:你确定你的代码没有问题?
虽然这题就这么过了,但我发现有一个小bug,当k大于结点个数,若先让fastk步,这不就越界了吗?怎么没有被检查出来?因此,严谨一点,补上一句代码

在这里插入图片描述

五、合并两个有序链表

Leetcode链接:合并两个有序链表

5.1 题目描述

在这里插入图片描述

5.2 做法

可以参考这篇博客 -->【算法基础】归并排序

代码实现

【不带哨兵位】

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    if (list1 == NULL) return list2;
    if (list2 == NULL) return list1;

    //创建两个指针分别遍历两个链表
    struct ListNode* cur1 = list1,*cur2 = list2;
    struct ListNode* head = NULL,*tail = NULL;

    while (cur1 && cur2)
    {
        if (cur1->val < cur2->val)
        {
            if (head == NULL)
            {
                head = tail = cur1;
            }
            else
            {
                tail->next = cur1;
                tail = tail->next;
            }
            cur1 = cur1->next;
        }
        else
        {
            if (head == NULL)
            {
                head = tail = cur2;
            }
            else
            {
                tail->next = cur2;
                tail = tail->next;
            }
            cur2 = cur2->next;
        }
    }

    if (cur1)
    {
        tail->next = cur1;
    }
    if (cur2)
    {
        tail->next = cur2;
    }

    return head;
}

在这里插入图片描述

【带哨兵位】

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    struct ListNode* cur1 = list1,*cur2 = list2;
    //创建一个带哨兵位的头结点
    struct ListNode* guard = (struct ListNode*)malloc(sizeof(struct ListNode));
    //定义一个指针来遍历
    struct ListNode* tail = guard;
    tail->next = NULL;

    while (cur1 && cur2)
    {
        if (cur1->val < cur2->val)
        {
            //尾插
            tail->next = cur1;
            tail = tail->next;
            cur1 = cur1->next;
        }
        else
        {
        	//尾插
            tail->next = cur2;
            tail = tail->next;
            cur2 = cur2->next;
        }
    }
	//如果cur1还有结点,直接尾插
    if (cur1)
    {
        tail->next = cur1;
    }
    //如果cur2还有结点,直接尾插
    if (cur2)
    {
        tail->next = cur2;
    }
    //最后销毁哨兵位头结点(防止内存泄漏)
    struct ListNode* head = guard->next;
    free(guard);
    return head;
}

在这里插入图片描述

六、分割链表

6.1 题目描述

LeetCode链接:分割链表

在这里插入图片描述

6.2 思路

  1. 创建两个带哨兵位的结点,一个存放<x的结点(Lguard),一个存放>x的结点(Bguard),并且创建两个指针来分别遍历<x的结点(Ltail)和>x的结点(Btail),最后再创建指针cur来遍历原结点。
  2. 接下来,如果cur指向的结点小于x,就将这个结点尾插到Lguard,否则就尾插到Bguard。
  3. 下一步将两个链表链接起来
  4. 最后释放掉两个哨兵位头结点

【动图展示】

在这里插入图片描述

为什么要将Btail的next置为NULL呢?
原因是:结点5在原结点的next是指向最后一个结点2的,如果不置为NULL,此时最后的Btail的next是指向2这个结点的,然后链接起来就会形成环。

6.3 代码实现

struct ListNode* partition(struct ListNode* head, int x)
{
    //创建两个带哨兵位的结点
    //一个存放<x的结点(Lguard),一个存放>x的结点(Bguard)
    struct ListNode* Lguard = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* Bguard = (struct ListNode*)malloc(sizeof(struct ListNode));
    //再创建两个指针来分别遍历<x的结点(Ltail)和>x的结点(Btail)
    struct ListNode* Ltail = Lguard,*Btail = Bguard;
    //初始化
    Ltail->next = NULL;
    Btail->next = NULL;
    //cur遍历原结点
    struct ListNode* cur = head;

    while (cur)
    {
        if (cur->val < x)
        {
            //尾插到Lguard
            Ltail->next = cur;
            Ltail = Ltail->next;
        }
        else
        {
            //尾插到BGuard
            Btail->next = cur;
            Btail = Btail->next;
        }
        cur = cur->next;
    }
    //链接
    Ltail->next = Bguard->next;
    //Bguard的尾结点要置为NULL,防止链表成环
    Btail->next = NULL;
    //释放哨兵位
    head = Lguard->next;
    free(Lguard);
    free(Bguard);

    return head;
}

在这里插入图片描述

七、链表的回文结构

7.1 题目描述

LeetCode链接:回文结构

在这里插入图片描述

7.2 思路

  1. 首先先使用快慢指针找到原链表的中间结点
  2. 再逆序中间结点以后的结点
  3. 将逆序后的结点与未被逆序的结点比较,如果相等返回true,不相等返回false

7.3 代码实现

//逆序函数
struct ListNode* reverseList(struct ListNode* head)
{
    if (head == NULL) return NULL;
    struct ListNode* prev = NULL;
    struct ListNode* cur = head;
    struct ListNode* next1 = cur->next;
    while (cur)
    {
    	//翻转
        cur->next = prev;
		//迭代
        prev = cur;
        cur = next1;
        //当nex1走到空指针就没必要走了
        if (next1)
            next1 = next1->next;
    }
    return prev;
}

bool isPalindrome(struct ListNode* head)
{
    //1。首先先使用快慢指针找到中间结点
    struct ListNode* slow = head,*fast = head;
    
    while (fast && fast->next)
    {
        fast = fast->next->next; 
        slow = slow->next;
    }
    //来到此处,slow就是中间结点
    //2.逆序中间结点以后的结点
    struct ListNode* rmid = reverseList(slow);
    //3.比较
    while (head && rmid)
    {
        if (head->val == rmid->val)
        {
            head = head->next;
            rmid = rmid->next;
        }
        else
            return false;
    }
    return true;
}

在这里插入图片描述

八、相交链表

LeetCode链接:相交链表

8.1 题目描述

在这里插入图片描述

8.2 思路

  1. 首先对于相交链表来说,它们最后一个结点的地址一定是相等的。所以可以先遍历一遍链表,判断它们的尾结点的地址是否相等
  2. 接下来为了能找到两链表相等的结点,可以先求出两个链表的大小(此步骤在第一步遍历链表可实现),让长链表先走它们的长度差步,为的就是它们能够在相对位置同时出发。
  3. 最后再遍历一遍链表,如果发现有两个结点地址相等,则返回对应结点

8.3 代码实现

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) 
{
    //1.遍历一遍链表,判断尾结点是否相等
    struct ListNode* tailA = headA,*tailB = headB;
    //lengthA和lengthB初始化为1,包含了最后一个结点
    int lengthA = 1,lengthB = 1;
    //找尾
    while (tailA->next != NULL)
    {
        lengthA++;
        tailA = tailA->next;
    }    
    while (tailB->next != NULL)
    {
        lengthB++;
        tailB = tailB->next;
    }
    
    //尾结点不相等,返回NULL
    if (tailA != tailB)
        return NULL;

    //2.先让长链表先走差距步
    //计算出差距步
    int gap = abs(lengthA - lengthB);
    //假设一开始headA最长
    struct ListNode* longest = headA;
    struct ListNode* shortest = headB;
    //可能存在headB比headA长的情况
    if (lengthA < lengthB)
    {
        longest = headB;
        shortest = headA;
    }
    //长链表先走差距步
    while (gap--)
    {
        longest = longest->next;
    }
    //最后让longest和shortest一起走,
    //如果发现它们的地址相等,说明这是一个相交链表
    while (longest != shortest)
    {
        longest = longest->next;
        shortest = shortest->next;
    }
    //当longest和shortest相等会来到此处
    return longest;//返回shortest也一样
}

在这里插入图片描述

  • 15
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值