【数据结构】6道经典链表面试题

news2024/11/24 3:24:41

目录

1.返回倒数第K个节点【链接】

​代码实现

2.链表的回文结构【链接】

代码实现 

3.相交链表【链接】

代码实现

4.判断链表中是否有环【链接】

代码实现

常见问题解析 

5.寻找环的入口点【链接】

代码实现1

​代码实现2

6.随机链表的复制【链接】

代码实现


1.返回倒数第K个节点【链接】

题目描述:

实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。

思路快指针先走k步,然后快指针和慢指针同时走,直到快指针走到NULL,此时慢指针的节点即为所求。

解析: 

代码实现

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

int kthToLast(struct ListNode* head, int k){
   struct ListNode*fast=head,*slow=head;  
   //快指针先走k步
   while(k--)
   {
    fast=fast->next;
   }
   //快慢指针一起走
   while(fast)
   {
    slow=slow->next;
    fast=fast->next;
   }
   return slow->val;
}

2.链表的回文结构【链接】

题目描述:

对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。

给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。

思路首先找到中间节点,将中间节点后半部分倒置,再分别从头结点和尾节点向中间遍历,看对应值是否相等。

这里需要用到曾经写过的找链表的中间节点函数和反转链表函数可参照【单链表的应用】

代码实现 

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class PalindromeList {
  public:
    /**
     * Definition for singly-linked list.
     * struct ListNode {
     *     int val;
     *     struct ListNode *next;
     * };
     */

    struct ListNode* middleNode(struct ListNode* head) {
        //创建快慢指针
        struct ListNode* slow = head;
        struct ListNode* fast = head;//如果有两个中间节点,则返回第二个中间节点
        while (fast && fast->next) {
            slow = slow->next;
            fast = fast->next->next;
        }
        //此时slow刚好指向中间节点
        return slow;
    }
    struct ListNode* reverseList(struct ListNode* head) {
        // 重新创建一个链表,将之前的链表进行头插即可
        struct ListNode* rphead = nullptr;
        // 进行指针变换
        struct ListNode* cur = head;
        while (cur != nullptr) {
            // 用于保存下一个节点地址
            struct ListNode* newnode = cur->next;
            // 头插
            cur->next = rphead;
            rphead = cur;
            cur = newnode;
        }
        return rphead; //返回新链表的头rhead
    }

    bool chkPalindrome(ListNode* A) {
        struct ListNode* mid = middleNode(A);
        struct ListNode* rmid = reverseList(mid);
        while (rmid && A) {
            if (rmid->val != A->val) {
                return false;
            }
            rmid = rmid->next;
            A = A->next;
        }
        return true;
    }
};

3.相交链表【链接】

题目描述:

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

示例1相交节点不是1,而是8,注意这里不能比值,因为两个不同节点的值可能一样,要比较节点的地址。

思路1:暴力求解,对于链表A中的每个节点,我们都遍历一次链表B看B中是否有相同节点,第一个找到的就是第一个公共节点,假设A链表有M个节点,B链表有N个节点,时间复杂度太高了为O(M*N)即O(N^2)。

思路2:先判断两个链表是否相交,可以通过判断两个链表最后一个节点地址是否相同,如果尾节点相同,说明两个链表一定相交,如果不相等,直接返回空指针。计算出两个链表的长度,让长链表先走相差的长度,然后让两个链表同时走,直到遇到相同的节点就是第一个公共节点,返回指向这个节点的指针。

解析: 

 

代码实现

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode *curA=headA,*curB=headB;
    int lenA=0,lenB=0;
    while(curA->next)
    {
        curA=curA->next;      
        ++lenA; 
    }
     while(curB->next)
    {
        curB=curB->next;     
        ++lenB; //此时B链表的长度少算一个
    }
    //尾节点不相等就是不相交
    if(curA!=curB)
    {
        return NULL;
    }
    lenA+=1;
    lenB+=1;
    //长的先走差距步,再同时走,第一个相等的就是交点
    //假设法
    int gap=abs(lenA-lenB);//求绝对值
    struct ListNode *longList=headA,*shortList=headB;//longList指向长链表,shprtList指向短链表
    //如果B比A长,再修改一下指针的指向,让longList指向长链表B,shprtList指向短链表A
    if(lenB>lenA)
    {
        longList=headB;
        shortList=headA;
    }
    while(gap--)//走差距步
    {
        longList=longList->next;
    }
    while(longList!=shortList)
    {
        longList=longList->next;
        shortList=shortList->next;
    }
    return shortList;//返回哪一个都可以
}

4.判断链表中是否有环【链接】

题目描述:

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

思路首先,让快慢指针fast、slow指向链表的头节点head, 让快指针fast一次向后移动两个节点,慢指针一次向后移动一个节点, 判断fast和slow是否走到同一个节点上数学上追击相遇问题,如果走到同一个节点上,就返回true。 

代码实现

bool hasCycle(struct ListNode *head) {
    struct ListNode*slow=head,*fast=head;
    while(fast&&fast->next)//当一个链表中没有环时,fast一定会移动到链表的结尾
    {
        slow=slow->next;
        fast=fast->next->next;
        if(slow==fast)
        {
            return true;
        }
    }
    return false;
}

常见问题解析 

1.快慢指针为什么会相遇,它们有没有可能错过,永远追不上?

 

不会错过。

假设slow进环时,fast 和 slow 的距离是N,追击过程中二者距离变化如下:

N->N-1->N-2->……->3->2->1->0

每追击一次,二者之间距离缩小1,距离为0即相遇。

2.慢指针slow一次移动一个节点,快指针一次移动多个节点(3,4,5……n)可行吗?

可行。

用快指针一次移动3个节点举例

假设slow进环时,fast 和 slow 的距离是N,追击过程中二者距离变化如下:

情况1:N->N-2->N-4->……->4->2->0(N为偶数)

情况2:N->N-2->N-4->……->5->3->1->-1(N为奇数)N为-1表示错过了,距离变成C-1(C为环的长度)

  • 如果C-1是偶数,新一轮追击就追上了
  • 如果C-1是奇数,那么就永远追不上

如果N是奇数并且C是偶数,那么就永远追不上,那这种条件存在吗?

假设slow进环前走的距离为L,设slow进环时,fast已经在环中转了x圈

slow走的距离:L

fast走的距离:L+x*C+C-N

由fast走的距离是slow的三倍产生的数学等式:3L=L+x*C+C-N

化简得:2L=(x+1)*C-N

我们发现偶数=(x+1)*偶数-奇数不成立,反证出当N是奇数时,C也一定是奇数

总结一下:一定能追上,N是偶数,第一轮就追上了;N是奇数,第一轮追不上,C-1是偶数,第二轮就追上了。

5.寻找环的入口点【链接】

题目描述:

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

思路1用两个指针head、meet分别指向链表的头节点和快慢指针相遇的节点,同时移动两个指针,当两个指针指向同一个节点时,该节点就是环的入口点 。

解析: 

假设环的长度为C,到达相遇点时,慢指针slow走过的距离为L+N(一圈之内肯定会被追上),快指针fast走过的距离为L+x*C+N (假设快指针走了x圈,N一定大于等于1),由fast走的距离是slow的2倍产生的数学等式:2*(L+N)=L+x*C+N  化简得:L=x*C-N

也就是说head到入口点的距离等于meet指针转x圈减去N的距离,head走到入口点,meet也刚好走到入口点,所以两个指针一起遍历,最终会同时到达入口点。

代码实现1

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;
}

思路2:让快慢指针相遇节点与下一个节点断开,然后将问题转化为两个链表的相交,环的入口点其实就是两个链表的相交点。

解析: 

代码实现2

 struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode *curA=headA,*curB=headB;
    int lenA=0,lenB=0;
    while(curA->next)
    {
        curA=curA->next;      
        ++lenA; 
    }
     while(curB->next)
    {
        curB=curB->next;     
        ++lenB; //此时B链表的长度少算一个
    }
    //尾节点不相等就是不相交
    if(curA!=curB)
    {
        return NULL;
    }
    lenA+=1;
    lenB+=1;
    //长的先走差距步,再同时走,第一个相等的就是交点
    //假设法
    int gap=abs(lenA-lenB);//求绝对值
    struct ListNode *longList=headA,*shortList=headB;//longList指向长链表,shprtList指向短链表
    //如果B比A长,再修改一下指针的指向,让longList指向长链表B,shprtList指向短链表A
    if(lenB>lenA)
    {
        longList=headB;
        shortList=headA;
    }
    while(gap--)//走差距步
    {
        longList=longList->next;
    }
    while(longList!=shortList)
    {
        longList=longList->next;
        shortList=shortList->next;
    }
    return shortList;//返回哪一个都可以
}

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;
         struct ListNode *newhead=meet->next;
         meet->next=NULL;        
         return getIntersectionNode(head,newhead);
        }
    }
    return NULL;
}

6.随机链表的复制【链接】

题目描述:

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 

深拷贝就是拷贝一个值和指针指向都跟当前链表一模一样的链表。 

思路: 拷贝节点到原节点的后面,再寻找途径处理random指针,处理完后对复制的节点拿下来尾插成新链表,返回新链表的头。

解析: 

代码实现

/**
 * Definition for a Node.
 * struct Node {
 *     int val;
 *     struct Node *next;
 *     struct Node *random;
 * };
 */

//拷贝节点插入在原节点的后面
struct Node* copyRandomList(struct Node* head) {
	struct Node*cur=head;
    while(cur)
    {
        struct Node*copy=(struct Node*)malloc(sizeof(struct Node));
        copy->val=cur->val;

        copy->next=cur->next;//注意这里顺序不能颠倒
        cur->next=copy;

        cur=copy->next;
    }
    //控制random
    cur=head;
    while(cur)
    {
        struct Node*copy=cur->next;
        if(cur->random==NULL)
        {
            copy->random=NULL;
        }
        else 
        {            
            copy->random=cur->random->next;
        }
        cur=copy->next;        
    }
    //把拷贝节点取下来尾插成为新链表,然后恢复原链表
    struct ListNode*copyhead=NULL,*copytail=NULL;
    cur=head;
    while(cur)
    {
        struct ListNode*copy=cur->next;
        struct ListNode*next=copy->next;
        if(copytail==NULL)
        {
            copyhead=copytail=copy;
        }
        else
        {
            copytail->next=copy;
            copytail=copytail->next;
        }
        cur->next=next;//恢复原链表,有没有这句代码都行!
        cur=next;
    }
    return copyhead;
}

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

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

相关文章

51单片机的无线通信智能车库门【proteus仿真+程序+报告+原理图+演示视频】

1、主要功能 该系统由AT89C51/STC89C52单片机LCD1602显示模块红外传感器光照传感器时钟模块步进电机蓝牙按键、LED、蜂鸣器等模块构成。适用于智能车库自动门、无线控制车库门等相似项目。 可实现功能: 1、LCD1602实时显示北京时间和自动/手动模式,以及验证是否成…

盘点2024年双十一最值得入手的好物,双十一购物清单宝藏好物推荐

随着2024年双十一购物节的脚步逐渐临近,消费者们正摩拳擦掌,准备迎接这场年度最大的购物狂欢。在这个特别的日子里,各大品牌纷纷推出前所未有的优惠活动,各种好物琳琅满目,让人眼花缭乱,为了帮助大家在这海…

机器学习、深度学习评价指标汇总:TP、TN、FP、FN、AP、mAP、IoU、mAP@3、Prec@10、 Acc@10

系列文章目录 文章目录 系列文章目录一、真正例(True Positive)、假正例(False Positive)、真负例(True Negative)和假负例(False Negative)是评估分类模型性能的重要概念。1. 定义2…

基于多种机器学习的酒店客户流失预测模型的研究与实现

文章目录 有需要本项目的代码或文档以及全部资源,或者部署调试可以私信博主项目介绍实现过程 有需要本项目的代码或文档以及全部资源,或者部署调试可以私信博主 项目介绍 项目背景: 在当今竞争激烈的酒店行业中,预测和防止客户流…

什么是PCB和PCBA,他们的区别是什么?

相信很多人对于PCB电路板并不陌生,可能是日常生活中也能经常听到,但对PCBA或许就不太了解,甚至把和PCB混淆起来。那么PCB是什么?PCBA是如何演变出来的?PCB与PCBA的区别又是什么呢?方案商如何高效找到供应商…

Ubuntu 22.04.4 LTS更换下载源

方法1:使用图形界面更换下载源 1. 打开软件和更新应用 2. 在Ubuntu 软件标签中,点击“下载自”旁边的下拉菜单,选择“其他” 3. 点击“选择最佳服务器”来自动选择最快的服务器 4. 选择服务器 5. 确定并关闭窗口,系统会提示您重新…

应变计的校准方法和周期是怎样的?

应变计的校准方法和周期应变计是用于测量物体应变的关键设备,广泛应用于工程测试和科学研究领域。正确的校准方法和合理的校准周期对于确保应变计测量结果的准确性至关重要。校准方法应变计的方法通常涉及以下几个步骤: 零点校准:这是校准过程…

跨界的胜利:机器学习与神经网络的物理之光

近日,2024年诺贝尔物理学奖颁发给了机器学习与神经网络领域的研究者,这是历史上首次出现这样的情况。这项奖项原本只授予对自然现象和物质的物理学研究作出重大贡献的科学家,如今却将全球范围内对机器学习和神经网络的研究和开发作为了一种能…

Robust多模态模型的开发

本文所涉及所有资源均在 传知代码平台 可获取。 目录 Robust 多模态模型:寻找遗失的模态! 一、研究背景 二、模型结构和代码 三、数据集介绍 六、性能展示 六、实现过程 七、运行过程 Robust 多模态模型:寻找遗失的模态! 近年来&a…

MPLS LDP协议

文章目录 LDP标签分发协议工作原理LDP应用倒数第二跳弹出 LDP标签分发协议 基于FEC自动分配标签构建LSP用于建立动态LSPLDP报文头部结构信息类型 工作原理 LDP工作过程 发送Hello消息用于发现邻居;UDP发送LSR_1主动发起并建立TCP连接;TCP进行建立主动方发…

Node.js安装与配置 [详细步骤(实践操作)]

安装与配置 安装Node.js一、(1).下载(可以直接在浏览器下的电脑)一、(2).下载(有些电脑不能在浏览器下载,比如:在浏览器下出来的是IDEA图标)二、安装三、配置环境变量 验证是否安装成功修改模块下载位置一、…

十几天8.7万粉丝,柒奶奶疯狂老太太图文笔记,为什么这么火?项目拆解!利用这个软件一键生成图文,赚钱到手软!

每个行业一有变化,那肯定会带来不少新机会,AI 绘画也是这样。就说小某书账号“人间清醒柒奶奶”吧,在有 AI 绘画之前,柒奶奶的角色和动作都得靠人用手画,可麻烦了。但现在呢,在网站上输入几个词儿就能轻轻松…

大贤3D家谱-保存、删除与重建

保存:对于创建的节点、内容进行本地存储。 重建:清除没有保存的数据,重现保存过的历史节点。 删除:删除包括该节点的所有子节点信息。 灵活的使用模式​ 为了保证软件的持续健康发展,我们采用了试用付费的模式。用…

微同城源码系统帮你轻松制作本地生活服务平台 带完整的安装代码包以及搭建部署教程

系统概述 微同城源码系统是一款基于Web技术开发而成的开源项目,旨在为广大用户提供一个灵活、高效且易于管理的本地生活服务平台构建工具。该系统采用了流行的前后端分离架构设计模式,前端部分利用React框架实现动态交互界面;后端则采用Spri…

什么是好的性能测试报告?

一、性能测试报告编写技巧 在对结果进行分析并得出结论之后,性能测试工程师要把它们以文字报告的形式发送给相关人员。这就是性能测试报告。除了书面文字之外,可能的话,公司还会召集人员开专门的会议进行报告讲解和结果分析。所以&#xff0…

redis同步解决 缓存击穿+缓存穿透 原理代码实现

缓存穿透 就是一个根本不存在的数据 请求过来,然后 发现缓存没有,就打到数据库,然后 数据库也没有,就会给数据库造成很大的压力 , 解决方案 就是老生常谈的 返回null值,或者布隆过滤器 我们说 返回nul…

linux查看k8s的开机启动状态 systemctl is-enabled 查看开机启动状态

查看k8s的开机启动状态 在Kubernetes中,通常使用systemd来管理服务的启动。但是,Kubernetes节点上的服务可能不是由systemd直接管理,而是通过kubelet服务来管理。因此,检查Kubernetes节点的开机启动状态,你需要检查ku…

MPI程序实例:FFT算法及应用

目录 一、一维串行FFT算法 二、二维串行FFT算法 三、并行FFT算法 四、应用示例 4.1、多项式相乘 4.2 循环矩阵方程组的求解 1965年,两位美国科学家J.W.Cooley和J.W.Tukey发明了一种有效计算傅氏变换的方法,被称为FFT(Fast Fourier Transform,快速傅里叶变换)…

java中StringBuffer类和StringBuilder类常用的api

目录 1.StringBuffer类Api 1).构造方法 2).append("添加的字符串内容") 3).insert(int 要添加数据到指定索引后,"要添加的字符串") 4).delete(int 起始索引位置,int 结束索引位置) 5).deleteCharA…

基于SpringBoot+Vue+Uniapp微信小程序的电子竞技信息交流平台设计与实现

详细视频演示 请联系我获取更详细的演示视频 项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念,提供了一套默认的配置,让开发者可以更专注于业务逻辑而…