单链表面试题思路分享二

news2025/1/23 2:02:25

单链表面试题思路分享二

  • 前言
    • 1.合并两个有序链表
      • 1.1 审题
      • 1.2 代码实现
      • 1.3 代码优化
    • 2. 链表的分割
      • 2.1 审题
      • 2.2 代码实现
    • 3. 链表的回文结构
      • 3.1 审题
      • 3.2 代码实现
    • 4. 链表相交
      • 4.1 审题
      • 4.2 代码实现
      • 4.3 方法二的实现
  • 5. 总结


前言

我们紧接上文单链表面试题分享一来看看本章我要分享的题目,共四个题目,我还是把它在力扣或者牛客网的链接交给大家:1.合并两个有序链表力扣21题-----2.链表的分割牛客网cc149-----3.链表的回文结构力扣234题-----4.链表相交力扣160题,本次分享还是和之前一样,代码用c语言实现,我只分享我自己的思路和我认为容易想错的点(我曾经错过的点),如若我的代码有问题但是这个题刚好可以编译可以,请大家评论区提出.



1.合并两个有序链表

1.1 审题

我们首先看题:
在这里插入图片描述

我们看见这道题的时候很容易和我们之前做的一道"合并两个有序数组"联系起来,但是问题是数组是可以通过下标来查找的,但是这个地方我们的链表只能"无脑"向后走,显然是不能用之前的结论的.***这里我们想到的就是用两个变量n1和n2,一个遍历list1,一个遍历list2,两个遍历同时走,我们可以再定义两个结构体指针head和tail,head是我们合并后数组的新头,tail用来不断往后走,这样我们就可以不用每次都遍历链表再插入.n1和n2谁指向的节点对应的值小就把谁放在tail上,然后值小的内个指针往后迭代,值大的保持不变.


在这里插入图片描述我们这样不断往后走直到n1或者n2其中一个为null,我们现在来实现一下代码



1.2 代码实现

#include<stdio.h>
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)//
{
    struct ListNode* n1 = list1;
    struct ListNode* n2 = list2;
    struct ListNode* head = NULL;
    struct ListNode* tail = NULL;//tail随时跟随新链表变动
    while (n1 && n2)
    {
        if (n1->val >= n2->val)
        {
            if (head == NULL)//head最开始为NULL,要先赋值
            {
                head = n2;
                tail = n2;
            }
            else
            {
                tail->next = n2;
                tail = n2;//tail不断往前走,这样就可以不用每次都遍历链表找到尾再插入了
            }
            n2 = n2->next;
        }
        else if (n1->val < n2->val)
        {
            if (head == NULL)
            {
                head = n1;
                tail = n1;
            }
            else
            {
                tail->next = n1;
                tail = n1;
            }
            n1 = n1->next;
        }
    }
    if (n2)//当n1或n2其中一个为空时就跳出来判断
    {
        tail->next = n2;//当n2首先为null时,我们就把list1后面所有的节点全部链接在tail后面
    }
    else if (n1)
    {
        tail->next = n1;//当n1首先为空时,我们把list2后面所有的节点全部链接在tail后面
    }
    return head;//最后返回我们新定义的头

}


1.3 代码优化

但是当我们提交代码后会发现它报错关于空指针解引用的问题,我们定义在出错的那一行,发现当我们原先的链表为空时,我们执行tail->next是对空指针解引用.所以我们来优化一下代码:

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)//
{
    if (list1 == NULL)//若这个地方不判断list是否为空指针,后面部分对tail解引用会报错为对空指针解引用
    {
        return list2;
    }
    if (list2 == NULL)
    {
        return list1;
    }
    struct ListNode* n1 = list1;
    struct ListNode* n2 = list2;
    struct ListNode* head = NULL;
    struct ListNode* tail = NULL;//tail随时跟随新链表变动
    while (n1 && n2)
    {
        if (n1->val >= n2->val)
        {
            if (head == NULL)//head最开始为NULL,要先赋值
            {
                head = n2;
                tail = n2;
            }
            else
            {
                tail->next = n2;
                tail = n2;//tail不断往前走,这样就可以不用每次都遍历链表找到尾再插入了
            }
            n2 = n2->next;
        }
        else if (n1->val < n2->val)
        {
            if (head == NULL)
            {
                head = n1;
                tail = n1;
            }
            else
            {
                tail->next = n1;
                tail = n1;
            }
            n1 = n1->next;
        }
    }
    if (n2)//当n1或n2其中一个为空时就跳出来判断
    {
        tail->next = n2;
    }
    else if (n1)
    {
        tail->next = n1;
    }
    return head;

}

只需要在代码最前面判断一下list是不是为空就好了.




2. 链表的分割

2.1 审题

先看题:
在这里插入图片描述

题目没有给用例,我们自己来假设一个.假如我们这个地方的链表给定为7->2->4->8->5->3.我们给一个X为4.那么我们将实现下图的功能:

在这里插入图片描述

我们的思路可能是先定义两个结构体指针n1和n2存放head的值,然后遍历链表,将节点指向的值小于X的链表放在b1当中,然后将节点指向的值小于X的节点放在n2当中,最后再将两个链表链接起来就可以了.但是当我们正在去实现代码的时候会发现,我们这样在原先链表上做这些操作很复杂,既要考虑节点的指向问题又要重新定义一个prev节点来记录前一个节点的位置.所以这种方法我们先放在一边看看有没有简单一点的方法.我们顺着刚才的思维再往下想,我们是不是可以重新定义两个结构体变量,为这个变量开辟一块和原先结构体占用空间大小一样的空间.然后我们不在原先的链表上操作而是在这两个新定义的"链表"中操作,这样就避免了在原先的链表上操作了. 在实现代码之前,我们有了之前几个题的经验会发现实现完代码总会有一些特殊的情况,比如头为空,或者对空指针解引用等等.这里我们创建两个变量的时候,我们再创建两个哨兵位来避免遇见这种问题. 这里如果有人不知道什么是哨兵位的话,我给大家一个链接快速了解哨兵位哨兵位作用和好处讲解
在这里插入图片描述



2.2 代码实现

ListNode* partition(ListNode* pHead, int x) {
        ListNode* nhead, * nend, * mhead, * mend;
        nhead = nend = (ListNode*)malloc(sizeof(ListNode)); //head和end指向同一个空间.
        mhead = mend = (ListNode*)malloc(sizeof(ListNode));
        mend->next = nend->next = NULL;//设置哨兵位方便尾插
        ListNode* cur = pHead;
        while (cur) {
            if (cur->val >= x) {
                mend->next = cur;
                mend = cur;//mend要往后走
            } else {
                nend->next = cur;
                nend = cur;//nend也要往后走
            }
            cur = cur->next;
        }
        nend->next = mhead->next;//将两个链表链接在一起
        mend->next = NULL;//这里因为mend为新链表的最后一个节点,它可能指向nhead中的元素.
        ListNode* newhead = nhead->next;
        free(nhead);
        free(mhead);
        return newhead;
    }

还有一点需要注意,上面这段代码中我最后把mend->next置为了NULL,这时因为我们有可能遇见下面这种情况,从而把我们的新链表变成了一个环:
在这里插入图片描述




3. 链表的回文结构

3.1 审题

先看题:
在这里插入图片描述

首先我们这里返回的是布尔类型true和false.这里题目值给出了我们的偶数个节点的情况,当我们写代码的时候还需要考虑奇数个节点.现在我们先来考虑偶数个节点的时候,如果链表为回文结构的话它是对称的,假如我们定义两个变量,一个变量放链表的前二分之一个节点,宁外一个链表放链表的后二分之一个节点再来判断这两个链表是否相同是不是能够完成任务,前二分之一个链表我们尾插原链表的节点,后二分之一个链表我们头插原链表的节点,这样我们就把两个链表变成一样的顺序了.这种方法按照逻辑是没有错的,但是我们说,这个题有没有优解,我们可以想到前一章我们讲过链表的中间节点和链表反转链表,我们仔细一想其实会发现这种头插尾插的形式还是比较麻烦的,我们可以利用前面的结论先找到链表的中间节点,再将中间节点后面的链表进行反转,这样也能得到我们想要的结果.

在这里插入图片描述我们一起遍历这两个链表,当其中一个链表为空时我们就停下来,这里我们先把偶数个节点的情况代码写出来,再去看奇数个:
之前我们用的找中间节点的办法是计数,我根据==感觉这样效率不高,所以这里我重新引入一种寻找中间节点的方法



3.2 代码实现

bool isPalindrome(struct ListNode* head)//先找到中间结点,再将中间结点以后的链表进行反转,利用前面链表题的结论
{
    struct ListNode* n = head;
    struct ListNode* m = head;
    while (m && m->next)//找到中间的节点的新方法,这个地方循环完后中间节点为n,可以自己画图验证
    {
        n = n->next;
        m = m->next->next;
    }//n为中间结点
    struct ListNode* cur = n;
    struct ListNode* prev = NULL;
    struct ListNode* next = n->next;
    while (cur)//反转链表
    {
        cur->next = prev;
        prev = cur;
        cur = next;
        if (next)
        {
            next = next->next;
        }
    }//prev为反转后的头
    struct ListNode* phead = head;
    while (phead && prev)//比较两个链表,当一个链表为NULL就停止
    {
        if (phead->val != prev->val)
        {
            return false;
        }
        else
        {
            phead = phead->next;
            prev = prev->next;
        }
    }
    return true;

}

我们判断偶数个的方法就已经实现出来了,再来思考奇数个节点应该怎么样判断:我们还是先找到中间的节点后反转再看看情况如何:
在这里插入图片描述

我们苦恼的点是比起上面的链表1,我们下面的链表2多了一个3,我们会认为在依次遍历我们的链表时会多出来一个节点,所以这种方法就不能采用,但是其实并不是这样,这个题很巧的地方在当我们的两个链表都走到2时,这时上面的链表1的next是指向链表2的节点3的,我们链表2的next也是指向节点3的,所以不会出现我们说的多出来一个节点的情况,所以我们对的偶数个节点的判断方法其实是适用于奇数个节点的 当我们做到这个地方的时候就很明了了,当我们以为一个方法是错误的时候不要把结论定死,先画图分析一下!




4. 链表相交

4.1 审题

我们先看题:
在这里插入图片描述

这是我第一次遇见两个链表指向同一个节点的问题,这里题目要我们干两件事,一是让我们判断这两个链表有没有公共节点,二是有公共节点返回相交的起始节点.这里我们把两个相交的链表分开来看要简洁明了一点:

在这里插入图片描述这里我们发现,如果两个链表有相交的节点,那么这两个链表的最后一个节点一定相同!注意这里的相同不是节点存储的数大小相同,这里是相同指的是两个结构体指针指向的是同一块空间,也就是同一个节点.所以我们的第一个问题就很好的解决了,我们只需要判断list1和list2最后一个节点相不相同就可以了.,我们还说,单链表指向的下一点只能有一个,所以我们不能说链表1和链表2在p点相交了,但是相交后面的节点可以不一样,比如像这样的X型结构是不存在的:
在这里插入图片描述

所以我们之前的判断可以说是没有问题的,思考到这里,我们会很容易想到一个方法来判断公共节点是否存在,那就是将链表1中的节点一一拿出来遍历链表2中的节点,如果相同就返回它,但是我们说这样做虽然可以做出来这道题,但是它的时间复杂度为O(N^2),而且思路比较平凡,没有创新型.所以我们宁僻稀径,我们想到要是这两个链表的长度一样就很好办了,假如两个链表节点数相同我们就可以从头直接一一对比链表1和链表2而不用遍历链表很多遍了.并且很巧的是我们之前判断它是否有相交节点的时候我们已经遍历过一遍两个链表了,这里我们在原来遍历的基础上加一个count1和count2来计数链表的节点数,两个链表相差多少个节点数我们就在节点数少的两个前面头插几个节点,将两个链表的结点数变成相同的.现在我们有大致的思路了就来实现一些代码.



4.2 代码实现

struct ListNode* getIntersectionNode(struct ListNode* headA, struct ListNode* headB)
{
    struct ListNode* n1 = headA;//定义n1和n2来计数
    struct ListNode* n2 = headB;
    struct ListNode* cur1 = headA;//定义cur1和cur2来遍历链表
    struct ListNode* cur2 = headB;
    int count1 = 0, count2 = 0;
    while (n1)//计算第一个链表有多少个结点
    {
        n1 = n1->next;
        count1++;
    }
    while (n2)//计算第二个链表有多少个结点
    {
        n2 = n2->next;
        count2++;
    }
    if (count1 > count2)//当两个链表结点数不一样,就把结点数少的链表头插几个结点变成和宁外一个链表结点数相同
    {
        int count = count1 - count2;
        while (count-- > 0)//这个过程在头插,list2头插
        {
            struct ListNode* newhead2 = (struct ListNode*)malloc(sizeof(struct ListNode));
            newhead2->val = 0;//头插的val设置为0
            newhead2->next = cur2;
            cur2 = newhead2;
        }
    }
    else if (count1 < count2)//也是头插,只不过是list1头插
    {
        int count = count2 - count1;
        while (count-- > 0)
        {
            struct ListNode* newhead1 = (struct ListNode*)malloc(sizeof(struct ListNode));
            newhead1->val = 0;
            newhead1->next = cur1;
            cur1 = newhead1;
        }
    }
    while (cur1)//当链表的结点数相同后,我们就找每个链表对应结点的值相不相同,
                // 如若相同就判断它们指向的结点是不是同一个
    {
        if (cur1->val == cur2->val)
        {
            if (cur1 == cur2)
            {
                return cur1;
            }
        }
        cur1 = cur1->next;
        cur2 = cur2->next;
    }
    return NULL;//如果没有返回cur1证明遍历了一整遍后都没有找到相交的节点.就返回null.
}

这个代码我们一提交就直接通过了,反应出我们的解题思路还是没有什么问题的,这个方法是小编自己做这个题时用的方法,但是这个题还有宁外一个解法



4.3 方法二的实现

我们说假如两个链表的长度是一样的我们就可以很好的解题了,这里我们引出宁一种方法,这种方法和方法一很相似,这里我们还是在原来遍历链表的基础上定义两个计数变量来记录链表1和链表2的节点个数,假如链表1比链表2多了n个节点:

在这里插入图片描述


现在我们来实现这段代码:

  struct ListNode* getIntersectionNode(struct ListNode* headA, struct ListNode* headB)
  {
      struct ListNode* taila = headA;
      struct ListNode* tailb = headB;
      int lena = 1;
      while (taila->next)
      {
          lena++;
          taila = taila->next;
      }
      int lenb = 1;
      while (tailb->next)
      {
          lenb++;
          tailb = tailb->next;
      }
      //不相交
      if (taila != tailb)
      {
          return NULL;
      }
      int gap = abs(lena - lenb);//abs为绝对值的意思
      //长的先走差距步,再同时找交点
      struct ListNode* longlist = headA;
      struct ListNode* shortlist = headB;
      if (lena < lenb)
      {
          shortlist = headA;
          longlist = headB;
      }
      while (gap--)
      {
          longlist = longlist->next;
      }
      while (longlist != shortlist)
      {
          longlist = longlist->next;
          shortlist = shortlist->next;
      }
      return longlist;
  }



5. 总结

本篇文章的四个题的难度相较于前一篇文章的难度是有所提升的,甚至我们还用到了前一章解题的一些结论.我们第一次做这种链表OJ题可能找不到头绪,不知道改从何下手,我想说这是很正常的现象!小编做题时也是往往抠破头皮也想不到解题之道,但是这种时候千万不要去看解析,还是上次提到的方法,先审题,再画图,有一定思路框架后再尝试写代码,遇见报错不要怕,慢慢调试它,我们说一个优秀的程序员思考和修改代码的时间是远远超过写代码的时间的!最后重要的事情再说一遍:链表题画图真的很重要,可以说画图画的好,解题只需一两秒.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/434125.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决PySide6/PyQT的界面卡死问题(PySide6/PyQT多线程

前言 问&#xff1a;在使用 PySide6 时候&#xff0c;会出现应用程序卡死的问题。 答&#xff1a;为什么会出现这个问题呢&#xff1f;PySide6 应用程序是基于事件驱动的&#xff0c;主线程负责处理GUI事件。如果有耗时的操作任务&#xff0c;GUI 事件将被阻塞&#xff0c;应用…

发送封包协议实现XXZ批量秒分解装备

通过发送封包&#xff0c;我们可以让一些反复的枯燥的行为变的简单&#xff0c;高效。 比如XXZ的萃取装备&#xff0c;我们可以一瞬间萃取大量的装备&#xff0c;而省去读条的过程。 我们来萃取一下看看效果 手动萃取是有读条的&#xff0c;那么如果很多装备的话&#xff0c;…

OAuth2.0 实践 Spring Authorization Server 搭建授权服务器 + Resource + Client

title: OAuth2.0 实践 Spring Authorization Server 搭建授权服务器 Resource Client date: 2023-03-27 01:41:26 tags: OAuth2.0Spring Authorization Server categories:开发实践 cover: https://cover.png feature: false 1. 授权服务器 目前 Spring 生态中的 OAuth2 授…

ArcGISPRO 和 ChatGPT集成思路

“我们如何一起使用 ArcGIS PRO 和 ChatGPT&#xff1f;”ArcGIS Pro 是一款功能强大的桌面 GIS 软件&#xff0c;用于制图、空间分析和数据管理。ChatGPT 是一种 AI 语言模型&#xff0c;可用于自然语言处理任务&#xff0c;例如文本生成和响应。 结合使用 ArcGIS Pro 和 Chat…

工业互联网业务知识

文章目录 背景第四次工业革命带动制造业产业升级主要工业大国不同路径 架构ISA95体系架构变革趋势基础通用架构数据采集平台 工业互联网应用软件工业互联网全要素连接产品视角&#xff1a;产销服务企业的业务流程企业数字化改造&#xff1a;车间级全要素连接 工业互联网的产品体…

Perl检查环境配置

最近部署Perl环境&#xff0c;但是不确定安装完成&#xff0c;看到有个内置监测的&#xff0c;记录下 perl bin/otrs.CheckModules.pl

数据类型及变量的定义、使用和注意事项

数据类型 计算机存储单元 变量的定义格式&#xff1a; 数据类型 变量名数据值; 我们知道计算机是可以用来存储数据的&#xff0c;但是无论是内存还是硬盘&#xff0c;计算机存储设备的最小信息单元叫“位( bit ) "&#xff0c;我们又称之为“比特位”&#xff0c;通常用…

生态-化学反应

生态&#xff0c;确实需要化学反应。但是如果不知道化学反应的各种前置条件&#xff0c;化学反应是不可能反应的。所以我们需要了解这些知识&#xff0c;并且把这些知识迁移到人类社会经济活动中。最厉害的人就是&#xff1a;范式提炼-范式迁移。 老贾就是不知道这些知识&#…

Poseidon Hash

之前我们介绍了zk友好的哈希函数Anemoi&#xff0c;今天我们介绍另一种zk友好的哈希函数Poseidon Poseidon采用 sponge/squeeze 结构&#xff0c;该结构吸纳万物并生成固定大小的输出&#xff0c;内部有一个状态 S ( s 1 , s 2 , . . . , s t ) S(s_1,s_2,...,s_t) S(s1​,s2…

真题详解(UML部署图)-软件设计(五十二)

真题详解&#xff08;地址索引&#xff09;-软件设计&#xff08;五十一)https://blog.csdn.net/ke1ying/article/details/130211684 瀑布模式&#xff1a;适应 开发大型项目&#xff0c;且需求明确。 演化模式&#xff1a;适应 对软件需求缺乏准确认知。 螺旋模式&#xff…

C语言CRC-32 MPEG-2格式校验函数

C语言CRC-32 MPEG-2格式校验函数 CRC-32校验产生4个字节长度的数据校验码&#xff0c;通过计算得到的校验码和获得的校验码比较&#xff0c;用于验证获得的数据的正确性。基本的CRC-32校验算法实现&#xff0c;参考&#xff1a; C语言标准CRC-32校验函数 不同应用规范通过对输…

阿里JAVA架构师面试136题含答案:JVM+spring+分布式+并发编程

此文包含 Java 面试的各个方面&#xff0c;史上最全&#xff0c;苦心整理最全Java面试题目整理包括基JVM算法数据库优化算法数据结构分布式并发编程缓存等&#xff0c;使用层面广&#xff0c;知识量大&#xff0c;涉及你的知识盲点。要想在面试者中出类拔萃就要比人付出更多的努…

Baklib在线知识库/帮助中心:让知识无限延伸

在今天这个信息爆炸的时代&#xff0c;各行各业都需要一个高效的知识管理系统来帮助他们更好地组织和分享知识。Baklib在线知识库/帮助中心就是这样一个优秀的工具&#xff0c;它可以帮助您轻松地创建、管理和分享知识&#xff0c;让您的团队和客户更加高效地工作。 什么是Bakl…

Linux进程控制【进程程序替换】

✨个人主页&#xff1a; Yohifo &#x1f389;所属专栏&#xff1a; Linux学习之旅 &#x1f38a;每篇一句&#xff1a; 图片来源 &#x1f383;操作环境&#xff1a; CentOS 7.6 阿里云远程服务器 Good judgment comes from experience, and a lot of that comes from bad jud…

ESXI 6.7全面系统教程~汇总

ESXI 6.7全面系统教程 许可证&#xff1a;0A65P-00HD0-375M1-M097M-22P7H esxi 是一个脱机系统&#xff0c;也是一个虚拟机系统与vmware 相比&#xff0c;它可以直接运行在硬件上&#xff0c;这样可以减少资源浪费&#xff0c;一般用于服务器上&#xff1b;下面是esxi 的完整…

PasteSpider之服务器介绍

在PasteSpider中服务器作为重要的一个对象&#xff0c;编译&#xff0c;构建&#xff0c;执行等都是服务器在执行&#xff0c;所以如何新建和服务器的各项属性介绍尤为重要&#xff01; 在菜单基础信息 服务器 点击 新增 按钮&#xff0c;可以看到如下图 我们从上面开始往下介…

SSR在天猫优品大促会场的探索实践

BBC 发现其网站加载时间每增加一秒&#xff0c;用户便会流失 10%。为提高页面的秒开率&#xff0c;我们不断探索着优化策略&#xff0c;仅仅在浏览器领域下的优化已经满足不了我们的极致要求&#xff0c;开始往服务端方向不断探索。本文将讨论业务接入SSR的几个问题&#xff1a…

《3-链表》

链表 引言&#xff1a; 存储数组需要内存空间连续&#xff0c;当我们需要申请一个很大的数组时&#xff0c;系统不一定存在这么大的连续内存空间。 而链表则更加灵活&#xff0c;不需要内存是连续的&#xff0c;只要剩余内存空间大小够用即可 1.定义 &#xff1a; 「链表 Lin…

设计模式-结构型模式之装饰模式

3. 装饰模式3.1. 模式动机一般有两种方式可以实现给一个类或对象增加行为&#xff1a;继承机制使用继承机制是给现有类添加功能的一种有效途径&#xff0c;通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的&#xff0c;用户不能控制…

CSS基础——盒子模型

目录 简介 盒子模型组成 内容区 内边距 边框 border-width border-color border-style border 外边距 负值 auto 简写属性 垂直外边距的重叠 浏览器默认设置 内联元素的盒子 简介 在网页中&#xff0c;一切都是可以看作为“盒子”。 在css处理网页的时候&…