【数据结构】链表的一些面试题

news2024/12/23 22:35:05

简单不先于复杂,而是在复杂之后。

在这里插入图片描述

链表面试题

  1. 删除链表中等于给定值 val 的所有结点。OJ链接
//1.常规方法

struct ListNode* removeElements(struct ListNode* head, int val) {
    struct ListNode* cur = head, *prev = NULL;
    while(cur)
    {
        if(cur->val == val)
        {
            //1.头删
            //2.非头删

            if(cur == head)
            {
                head = head->next;
                free(cur);
                cur = head;
            }
            else
            {
               prev-> next = cur->next;
               free(cur);
               cur = prev-> next;
            }

        }
        else
        {
            prev = cur;
            cur = cur->next;
        }
    } 

    return head;
}
//2.新思路
//创建一个新链表,把非val的结点尾插到新链表
struct ListNode* removeElements(struct ListNode* head, int val) {
   struct ListNode* newhead =NULL, *cur = head, *tail = NULL;
   while(cur)
   {
       if(cur->val != val)
       {
           if(tail == NULL)
           {
               newhead = tail = cur;
           }
           else
           {
               tail->next = cur;
               tail = tail->next;
           }
           cur = cur->next;
       }
       else
       {
            struct ListNode* del = cur;
            cur = cur->next;
            free(del);
       }
   }

   //最后的节点是val就会出现此问题

   if(tail)
   {
       tail->next = NULL;
   }


   return newhead;
}

小技巧:在涉及到链表的题的时候调试不太方便,我们需要快速构建一个链表。

int main()
{
    struct ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));
    assert(n1);
      struct ListNode* n2 = (struct ListNode*)malloc(sizeof(struct ListNode));
    assert(n2);
      struct ListNode* n3 = (struct ListNode*)malloc(sizeof(struct ListNode));
    assert(n3);
      struct ListNode* n4 = (struct ListNode*)malloc(sizeof(struct ListNode));
    assert(n4);
      struct ListNode* n5 = (struct ListNode*)malloc(sizeof(struct ListNode));
    assert(n5);
      struct ListNode* n6 = (struct ListNode*)malloc(sizeof(struct ListNode));
    assert(n6);
      struct ListNode* n7 = (struct ListNode*)malloc(sizeof(struct ListNode));
    assert(n7);

    n1->next = n2;
    n2->next = n3;
    n3->next = n4;
    n4->next = n5;
    n5->next = n6;
    n6->next = n7;
    n7->next = NULL;

    n1->val = 1;
    n2->val = 2;
    n3->val = 6;
    n4->val = 3;
    n5->val = 4;
    n6->val = 5;
    n7->val = 6;

    return 0;
}

带哨兵位的链表:

  • 哨兵节点(Sentinel Node): 这是一个特殊的节点,它不包含实际数据,仅用于简化代码逻辑。哨兵节点通常作为链表的头部存在,这意味着链表中始终有一个节点,即使链表为空。哨兵节点的存在简化了对链表头部的特殊处理,因为无论链表是否为空,我们都可以始终通过哨兵节点来引导链表的访问。
  • 优点: 哨兵节点可以简化代码实现,避免在对头部进行操作时需要额外的条件检查。

不带哨兵位的链表:

  • 特殊处理头部: 在不使用哨兵节点的情况下,需要特殊处理链表头部,因为在空链表或者插入第一个节点时,需要进行额外的条件检查,以确保正确处理链表头部。
  • 优点: 节省了一个节点的空间开销,因为没有用于哨兵的额外节点。

带哨兵位和不带哨兵位的区别:

在这里插入图片描述

//利用带哨兵位的头节点的链表可以免去判空的步骤

struct ListNode* removeElements(struct ListNode* head, int val) {
   struct ListNode* cur = head;
   struct ListNode* guard = (struct ListNode*)malloc(sizeof(struct ListNode));
   struct ListNode* tail = guard;
   while(cur)
   {
       if(cur->val != val)
       {
            tail->next = cur;
            tail = tail->next;
            cur = cur->next;
       }
       else
       {
            struct ListNode* del = cur;
            cur = cur->next;
            free(del);
       }
   }

   //最后的节点是val就会出现此问题

   if(tail)
   {
       tail->next = NULL;
   }

    head = guard->next;
    free(guard);
    return head;
}

替代之前实现单链表时候传参用的二级指针有两种方式:

  • 返回新的链表头指针
  • 设计为带哨兵位的链表

(单链表在实际应用中很少带头,OJ题链表基本不带头)

  1. 反转一个单链表。OJ链接

在这里插入图片描述

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

   while(cur)
   {
        struct ListNode* next = cur->next;
        cur->next = newhead;
        newhead = cur;

        cur = next;
   }
   return newhead;
}
//思路2:将链表的指针指向反转
struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* prev = NULL;
    struct ListNode* cur = head;
    while(cur)
    {
        struct ListNode* next = cur->next;
        cur->next = prev;
        //迭代器
        prev = cur;
        cur = next;
    }
    return prev;
}
  1. 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。OJ链接

在这里插入图片描述

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

    return slow;
}
  1. 输入一个链表,输出该链表中倒数第k个结点。OJ链接

在这里插入图片描述

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
    // write code here
    struct ListNode* slow, *fast;
    slow = fast = pListHead;

    while(k--)
    {
       //判断k大于链表长度
        if(!fast)
        {
            return NULL;
        }
         fast = fast->next;
    }
    while(fast)
    {
        slow = slow->next;
        fast = fast->next;

    }

    return slow;
}
  1. 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有结点组成的。OJ链接
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) 
{
    struct ListNode* guard = (struct ListNode*)malloc(sizeof(struct ListNode));
    guard->next = NULL;
    struct ListNode* tail = guard;
    struct ListNode* cur1 = list1, *cur2 = list2;
    while(cur1 && cur2)
    {
        if(cur1->val < cur2->val)
        {
            tail->next = cur1;
            cur1 = cur1->next;
        }
        else
        {
            tail->next = cur2;
            cur2 = cur2->next;
        }
        tail = tail->next;



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

    struct ListNode* head = guard->next;
    free(guard);
    return head;
}
  1. 编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大小或等于x的结点之前。OJ链接

在这里插入图片描述

在这里插入图片描述

class Partition {
  public:
    ListNode* partition(ListNode* pHead, int x) {
        // write code here
       struct ListNode* lessTail,*lessGuard,*greaterTail,*greaterGuard;
       lessTail = lessGuard = (struct ListNode*)malloc(sizeof(struct ListNode));
       greaterTail = greaterGuard = (struct ListNode*)malloc(sizeof(struct ListNode));
       struct ListNode* cur = pHead;

       greaterGuard->next = NULL;
       lessGuard->next = NULL;

       while(cur)
       {
        if(cur->val < x)
        {
            lessTail->next = cur;
            lessTail = lessTail->next;
        }
        else 
        {
            greaterTail->next = cur;
            greaterTail = greaterTail->next;
        }
        cur = cur->next;
       }

       lessTail->next = greaterGuard->next;
       greaterTail->next = NULL;

       pHead = lessGuard->next;
       free(greaterGuard);
       free(lessGuard);

       return pHead;
    }
};
  1. 链表的回文结构。OJ链表

在这里插入图片描述

class PalindromeList {
  public:
    struct ListNode* middleNode(struct ListNode* head) {
        struct ListNode* fast, *slow;
        fast = slow = head;
        while (fast && fast->next) {
            slow = slow->next;
            fast = fast->next->next;
        }

        return slow;
    }

    struct ListNode* reverseList(struct ListNode* head) {
        struct ListNode* prev = NULL;
        struct ListNode* cur = head;
        while (cur) {
            struct ListNode* next = cur->next;
            cur->next = prev;
            //迭代器
            prev = cur;
            cur = next;
        }
        return prev;
    }

    bool chkPalindrome(ListNode* head) {
        // write code here
        struct ListNode* mid = middleNode(head);
        struct ListNode* rmid = reverseList(mid);

        while(head && rmid)
        {
            if(head->val != rmid->val)
                return false;

            head = head->next;
            rmid = rmid->next;
        }

        return true;

    }
};
  1. 输入两个链表,找出它们的第一个公共结点。OJ链表

在这里插入图片描述

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode* curA = headA, *curB = headB;

    if(headA == NULL || headB == NULL)
    {
        return NULL;
    }

    int lenA = 1;
    while(curA->next)
    {
        curA = curA->next;
        lenA++;
    }

      int lenB = 1;
    while(curB->next)
    {
        curB = curB->next;
        lenB++;
    }

    if(curA != curB)
    {
        return NULL;
    }

    struct ListNode* longList = headA,*shortList = headB;
    if(lenA < lenB)
    {
        longList = headB;
        shortList = headA;
    }

    int gap = abs(lenA - lenB);
    while(gap--)
    {
        longList = longList->next;
    }

    while(longList != shortList)
    {
        longList = longList->next;
        shortList = shortList->next;
    }

    return longList;
}
  1. 给定一个链表,判断链表中是否有环。OJ链表

在这里插入图片描述

bool hasCycle(struct ListNode *head) {
    struct ListNode* fast, *slow;
    fast = slow = head;

    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;

        if(slow == fast)
            return true;
    }

    return false;
}

【思路】

快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表起始位置开始运行,如果链表带环则一定会在环中相遇,否则快指针率先走到链表的末尾。

【扩展问题】

  • 为什么快指针每次走两步,慢指针走一步可以?

假设链表带环,两个指针最后都会进入环,快指针先进环,慢指针后进环。当慢指针刚进环时,可能就和快指针相遇了,最差情况下两个指针之间的距离刚好就是环的长度。

此时,两个指针每移动一次,之间的距离就缩小一步,不会出现每次刚好是套圈的情况,因此:在慢指针走到一圈之前,快指针肯定是可以追上慢指针的,即相遇。

  • 快指针一次走3步,走4步,…n步行吗?

    • 快指针一次走 3 步:

      假设slow进环后,fast和slow之间差距N,追赶距离就是N,每追赶一次,之间的距离缩小2步

      如果N是偶数,距离一定会减少到0,也就是两个指针相遇;

      如果N是奇数,距离会减少到1,fast会越过slow指针,fast和slow之间得距离变成了-1,也就是C-1,(C-1是环长度),如果C-1是偶数,再追一圈就可以追上,如果C-1是奇数,永远追不上;

  1. 给定一个链表,返回链表开始入环的第一个结点。如果链表无环,则返回 NULL。OJ链接

在这里插入图片描述

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

        if(slow == fast)
        {
            struct ListNode* meet = slow;

            while(meet != head)
            {
                meet = meet->next;
                head = head->next;
            }

            return meet;
        }
    }
    return NULL;
}

在这里插入图片描述

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
  struct ListNode* curA = headA, *curB = headB;
  
  int lenA = 1;
  while(curA->next)
  {
      curA = curA->next;
      lenA++;
  }

  int lenB = 1;
  while(curB->next)
  {
      curB = curB->next;
      lenB++;
  }

  if(curA != curB)
  {
      return NULL;
  }

  struct ListNode* longList = headA, *shortList = headB;
  if(lenA < lenB)
  {
      longList = headB;
      shortList = headA;
  }

  int gap = abs(lenA - lenB);

  while(gap--)
  {
      longList = longList->next;
  }

  while(longList != shortList)
  {
      longList = longList->next;
      shortList = shortList->next;
  }

  return longList;
}

struct ListNode *detectCycle(struct ListNode *head) {
   struct ListNode* slow, *fast;
   slow = fast = head;

   while(fast && fast->next)
   {
       slow = slow->next;          
       fast = fast->next->next;

       if(slow == fast)
       {
            struct ListNode* meet = slow;
            struct ListNode* next = meet->next;
            meet->next = NULL;
            
            struct ListNode* entry =  getIntersectionNode(head,next);
            meet->next = next;
            return entry;
         
       }
       
   }
   return NULL;
   
}

在这里插入图片描述

  1. 给定一个链表,每个结点包含一个额外增加的随机指针,该指针可以指向链表中的任何结点或空结点。

​ 要求返回这个链表的深度拷贝。OJ链接

struct Node* copyRandomList(struct Node* head) {
	// 1.插入copy节点
    struct Node* cur = head;
    struct Node* copy;
    struct Node* next;
    while(cur)
    {
        // 复制链接
        next = cur->next;
        copy = (struct Node*)malloc(sizeof(struct Node));
        copy->val = cur->val;

        cur->next = copy;
        copy->next = next;

        // 迭代往后走
        cur = next;
    }

    //2. 更新copy->random
   cur = head;
   while(cur)
   {
       copy = cur->next;

       if(cur->random == NULL)
            copy->random = NULL;
       else
            copy->random = cur->random->next;

       //迭代
       cur = copy->next;
   } 

   //3. copy节点要解下来链接在一起,然后恢复原链表
   struct Node* copyHead = NULL, *copyTail = NULL;
   cur = head;
   while(cur)
   {
       copy = cur->next;
       next = copy->next;

       //取节点尾插
       if(copyTail == NULL)
       {
           copyHead = copyTail = copy;
       }
       else
       {
           copyTail->next = copy;
           copyTail = copyTail->next;
       }

       //恢复原链表链接
       cur->next = next;

       //迭代
       cur = next;
   }

   return copyHead;
}
  1. 其他。ps:链表的题当前因为难度及知识面等等原因还不适合当前学习,下面有OJ链接。

Leetcode OJ链接 + 牛客 OJ链接

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

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

相关文章

WPF入门到跪下 第十一章 Prism(五)IOC的依赖注入

IOC的依赖注入 一、构造函数方式的依赖注入 以项目启动时MainWindowViewModel的依赖注入为例&#xff0c;默认情况下Prism框架的项目&#xff0c;在打开窗口时会自动匹配主窗口的视图模型类&#xff08;PrismApplication启动&#xff09;&#xff0c;这里是MainWindowViewMod…

外汇天眼:纽约总检察长起诉花旗银行,指责其未能保护欺诈受害者

纽约总检察长莉蒂西亚詹姆斯今天起诉花旗银行&#xff0c;指责其未能保护并拒绝偿还欺诈受害者。该诉讼声称&#xff0c;花旗银行没有实施强有力的在线保护措施来阻止未经授权的账户劫持&#xff0c;误导账户持有人关于账户被黑客攻击并且资金被盗后的权利&#xff0c;并非法地…

uniapp多格式文件选择(APP,H5)

uniapp多格式文件选择&#xff08;APP&#xff0c;H5&#xff09; 背景实现代码实现运行结果注意事项 尾巴 背景 从手机选择文件进行上传是移动端很常见的需求&#xff0c;在原生开发时由于平台专一性很容易实现。但是用uniapp开发官方提供的API在APP平台只能选择图片和视频&a…

负载均衡下的webshell连接

一、环境配置 1.在Ubuntu上配置docker环境 我们选择用Xshell来将环境资源上传到Ubuntu虚拟机上&#xff08;比较简单&#xff09; 我们选择在root模式下进行环境配置&#xff0c;先将资源文件复制到root下&#xff08;如果你一开始就传输到root下就不用理会这个&#xff09; …

手把手教测试,全网内容最全最深-jmeter-Recording Controller(录制控制器)

5.1.6.14.Recording Controller(录制控制器) 第一步&#xff1a; 第二步&#xff1a;点击启动按钮&#xff0c;生成证书。证书在jmeter的bin目录下。 第三步&#xff1a;设置代理 第四步&#xff1a;抓取https包需要安装证书&#xff0c;在浏览器edge中安装 未完待续。。。 手…

Django4.2(DRF)+Vue3 读写分离项目部署上线

文章目录 1 前端2 后端2.1 修改 settings.py 文件关于静态文件2.2 关于用户上传的文件图片 3 Nginx4 镜像制作4.1 nginx4.3 Django镜像4.3.1 构建 5 docker-compose 文件内容 1 前端 进入前端项目的根目录&#xff0c;运行如下命令进行构建 npm run build构建完成后&#xff…

金田金业教你如何看懂国际黄金价格走势图

对于黄金投资者来说&#xff0c;看懂国际黄金价格走势图是至关重要的。通过观察走势图&#xff0c;可以了解金价的实时动态&#xff0c;预测未来的走势&#xff0c;从而做出相应的投资决策。本文将详细解析如何看懂国际黄金价格走势图。 一、国际黄金价格走势图的基本构成 国…

10s 内得到一个干净、开箱即用的 Linux 系统

安装 使用官方脚本安装我的服务器不行 官方脚本 mkdir instantbox && cd $_ bash <(curl -sSL https://raw.githubusercontent.com/instantbox/instantbox/master/init.sh) 下面是我的完整安装过程 mkdir /opt/instantbox cd /opt/instantbox 1.脚本文件 (这个没…

12.MySql服务

目录 1. 什么是数据库 1.1. 数据&#xff1a; 1.2. 数据库&#xff1a; 2. mysql概述 3. 版本及下载 4. yum仓库安装 4.1. 添加yum源 4.2. 安装 5. 本地RPM包安装 5.1. 使用迅雷下载集合包 5.2. 上传数据 5.3. 安装 6. 生产环境中使用通用二进制包安装 6.1. 作用…

如何应对Android面试官-> CoordinatorLayout详解,我用 Behavior 实现了手势跟随

前言 本章主要讲解下 CoordinatorLayout 的基础用法、工作原理和自定义Behavior 原理 使用很简单&#xff0c;百度上可以搜索下基础使用 协调者布局的功能 作为应用的顶层布局作为一个管理容器&#xff0c;管理与子 View 或者子 View 之间的交互处理子控件之间依赖下的交互处…

ChatGPT可与自定义GPTs一起使用,智能AI代理时代来啦!

1月31日凌晨&#xff0c;OpenAI在社交平台公布了一个超强新功能&#xff0c;可以在ChatGPT中输入“GPTs名字”的方法&#xff0c;调用多个自定义GPTs一起协同工作。 例如&#xff0c;我想开发一款社交APP&#xff0c;1&#xff09;可以先用专业分析GPTs做一下市场调研&#xf…

呼吸灯--FPGA

目录 1.breath_led.v 2.tb_breath_led.v 呼吸灯就是从完全熄灭到完全点亮&#xff0c;再从完全点亮到完全熄灭。具体就是通过控制PWM的占空比控制亮灭程度。 绘制PWM波的步骤就是&#xff0c;首先灯是在第一个时钟周期保持高电平熄灭状态&#xff0c;在第二个时钟周期保持1/1…

OpenGL 入门(三)— Textures(纹理)

文章目录 前言纹理环绕方式纹理过滤多级渐远纹理(Mipmap)加载与创建纹理stb_image.h库生成纹理 应用纹理顶点着色器片元着色器完整脚本 纹理单元 前言 纹理(Texture)。纹理是一个2D图片&#xff08;甚至也有1D和3D的纹理&#xff09;&#xff0c;它可以用来添加物体的细节。 你…

Visual Studio无法调试Unity的可能原因和解决办法

问题描述&#xff1a; 在unity和vs都安装了相关插件的情况下&#xff0c;vs在启动了“附加到Unity”后却并没有进入调试模式。 可能的原因及解决办法&#xff1a; 1、Unity未设置成调试模式 将Unity编辑器的右下角这个debug标志设置成debug模式: 设置后变成了&#xff1a; 注…

最小步数模型

AcWing 1107. 魔板 #include <bits/stdc.h> using namespace std;char g[2][4]; const int N 10; unordered_map<string, pair<char, string> > pre; unordered_map<string, int> d;void Set(string s) {for(int i0; i<4; i) g[0][i] s[i];for(in…

深入理解指针(1)

⽬录&#xff1a; 1. 内存和地址 2. 指针变量和地址 3. 指针变量类型的意义 4. const修饰指针 5. 指针运算 6. 野指针 7. 指针的使⽤和传址调⽤ 1. 内存和地址 1.1 内存 在讲内存和地址之前&#xff0c;我们想有个⽣活中的案例&#xff1a; 假设有⼀栋宿舍楼&#xff…

Nodejs基于Vue的物料物资生产销售制造执行系统

本系统具有的功能有&#xff1a; 后台&#xff1a; 1. 用户登录模块&#xff1a;账号密码的登录验证提示。 2. 基础数据模块&#xff1a;部门、员工、存货类型、计量单位、存货档案的增删改查。 3. 工程数据模块&#xff1a;工序、工艺路线、BOM管理的增删改查。 4. …

初探分布式链路追踪

本篇文章&#xff0c;主要介绍应用如何正确使用日志系统&#xff0c;帮助用户从依赖、输出、清理、问题排查、报警等各方面全面掌握。 可观测性 可观察性不单是一套理论框架&#xff0c;而且并不强制具体的技术规格。其核心在于鼓励团队内化可观察性的理念&#xff0c;并确保由…

聊聊java中的Eureka和Nacos

本文主要来自于黑马课程中 1.提供者与消费者 在服务调用关系中&#xff0c;会有两个不同的角色&#xff1a; 服务提供者&#xff1a;一次业务中&#xff0c;被其它微服务调用的服务。&#xff08;提供接口给其它微服务&#xff09; 服务消费者&#xff1a;一次业务中&#xff0…

JAVA和C#怎么开发SECS/GEM:recipe配方处理 S7F1、S7F19

recipe是什么内容呢&#xff1f; recipe是机台加工不同产品时的对应程式&#xff0c;指的是由制造工程师提前在机台上设置&#xff0c;并且EAP控制生产时会自动根据货的类型选择并控制机台按照制造工程师提前设置的方式进行加工。 recipe也称为配方&#xff0c;配方是怎么来的…