单链表面试题思路分享二

这篇具有很好参考价值的文章主要介绍了单链表面试题思路分享二。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


前言

我们紧接上文单链表面试题分享一来看看本章我要分享的题目,共四个题目,我还是把它在力扣或者牛客网的链接交给大家: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题可能找不到头绪,不知道改从何下手,我想说这是很正常的现象!小编做题时也是往往抠破头皮也想不到解题之道,但是这种时候千万不要去看解析,还是上次提到的方法,先审题,再画图,有一定思路框架后再尝试写代码,遇见报错不要怕,慢慢调试它,我们说一个优秀的程序员思考和修改代码的时间是远远超过写代码的时间的!最后重要的事情再说一遍:链表题画图真的很重要,可以说画图画的好,解题只需一两秒.文章来源地址https://www.toymoban.com/news/detail-422560.html

到了这里,关于单链表面试题思路分享二的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构练习】链表面试题锦集一

    目录 前言: 1. 删除链表中所有值为key的节点  方法一:正常删除,头结点另外讨论  方法二:虚拟头结点法  方法三:递归 2.反转链表  方法一:双指针迭代   方法二:递归法 3.链表的中间结点   方法:快慢指针法 4. 链表中倒数第k个结点  方法:快慢指针方法 5.合并两个

    2024年02月11日
    浏览(37)
  • 【数据结构练习】链表面试题集锦一

    目录 前言: 1. 删除链表中所有值为key的节点  方法一:正常删除,头结点另外讨论  方法二:虚拟头结点法  方法三:递归 2.反转链表  方法一:双指针迭代   方法二:递归法 3.链表的中间结点   方法:快慢指针法 4. 链表中倒数第k个结点  方法:快慢指针方法 5.合并两个

    2024年02月11日
    浏览(38)
  • 【链表面试题】解决环形链表和相交链表问题

    在力扣上发现这样的几道题,尝试做了一下,也发现了一个关于这类题的一种做法:快慢指针的使用。废话不多说,上例题 目录 一、环形链表 1.定义(概念) 2.如何判断是否为环形链表 1.快慢指针 2.为什么快指针一次走两个结点 3.例题分析 二、相交链表 1.例题分析 2.解法分

    2023年04月17日
    浏览(43)
  • 【C++初阶】前言——C++的发展简述及学习方法分享

     ========================================================================= 主页点击直达: 个人主页 我的小仓库: 代码仓库 C语言偷着笑: C语言专栏 数据结构挨打小记: 初阶数据结构专栏 Linux被操作记: Linux专栏 LeetCode刷题掉发记: LeetCode刷题 算法: 算法专栏  C++头疼记: C++专栏 ====

    2024年02月08日
    浏览(60)
  • 带头节点的单链表的思路及代码实现

    单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) +指针(指示后继元素存储位置,元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。) 以上是

    2023年04月08日
    浏览(36)
  • 【数据结构】带头双向循环链表---C语言版(单链表我们分手吧,不要再找我玩了!!!)

    在前面我们学习实现了单链表(无头单向不循环链表),这里我们引入带头双向循环链表 很明显这两种链表的结构截然不同,但都是作为链表最常使用链表结构 前者因其结构上的缺点而作为面试考题的常驻嘉宾,而且复杂麻烦 后者则是以结构最优著称,实现起来也是非常的

    2024年02月10日
    浏览(46)
  • 【数据结构】单链表经典算法题的巧妙解题思路

    目录 题目 1.移除链表元素 2.反转链表 3.链表的中间节点 4.合并两个有序链表 5.环形链表的约瑟夫问题 解析 题目1:创建新链表 题目2:巧用三个指针 题目3:快慢指针 题目4:哨兵位节点 题目5:环形链表   介绍完了单链表,我们这次来说几个经典的题目,本篇的题目衔接下一

    2024年04月28日
    浏览(54)
  • 国科大. 深度学习:期末试题与简要思路分析

    监督学习: 从标记的训练数据来推断一个功能的机器学习任务。 无监督学习: 根据类别未知(没有被标记)的训练样本解决模式识别中的各种问题,称之为无监督学习。 强化学习: 用于描述和解决智能体(agent)在与环境的 交互 过程中通过学习策略以达成回报最大化或实现

    2024年02月11日
    浏览(37)
  • 华为OD机试题 - 五键键盘(JavaScript)| 含思路

    华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单 华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典 【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南 华为od机试,独家整理 已参加机试人员的实战技巧 参加华为od机试,一定要注意不要完全背诵代码,需要

    2024年02月11日
    浏览(38)
  • 【面试准备 算法题】用快排的思路对单链表进行排序(不能进行值拷贝)

    最近面试碰到这个题目感觉很有意思,既考察二分/递归的思想,也考察链表的操作,尤其对于边界情况的处理需要细心 给定单链表进行排序(链表节点定义如上) 不能通过值拷贝来实现元素交换(必须通过修改next指针实现 元素位置排序 )

    2024年02月13日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包