链表算法题(下)

news2024/11/15 23:50:49

在链表算法题(上)长中我们已经学习了一系列的链表算法题,那么在本篇中我们将继续来学习链表的算法题,接下来就继续来破解链表的算法题吧!


1.相交链表

160. 相交链表 - 力扣(LeetCode)

通过以上的题目的描述该算法题要我们实现的代码功能是判断两条链表是否相交,如果相较的话就返回相较节点的指针,不相较就返回NULL

那么在实现该算法题的代码之前先要来分析如何实现判断是否是相交链表

首先来看相交链表有什么特征呢?来看以下的示例

从以上的示例就可以看出当两个链表是相交的,那么在这两条链表内一定会有两个节点的下一个节点是相同的。而如果链表不是相交的,那么就不会有两个节点的下一个节点是相同的

当两个链表是相较的时候我们需要返回的就是以上这个节点,那么该如何来找出这个相交的节点呢?

在此我们的解决方法是先定义两个指针变量一开始分别指向两个链表的第一个节点,之后通过各遍历一次两条链表,之后就得到这两条链表各自的节点个数,之后将得到的两个节点数大的减小的得到两链表长度的差值,将节点个数多的链表的定义的节点指针往后走之前得到的差值,最后将两个链表的指针同时往后遍历,当两个指针相同时就说明这两个链表为相交链表,否则就不相交

例如以下示例:

首先A链表个数为5,B节点个数为6,两个链表节点差值就为1

在定义两个指向两个;链表的第一个节点的指针pcur1和pcur2,由于B链表为节点个数多的链表因此将B链表的指针pcur2先向后走1步

之后让pcur1和pcur2同时向后遍历,之后pcur1和pcur2同时指向c1节点,这就可以说明A和B这两个链表是相交链表

接下来我们就来实现该算法题的代码
在以下得到两个链表的节点数差值使用的是库函数abs,该函数能计算出差值的绝对值

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) 
{
    ListNode* pcur1=headA;
    ListNode *pcur2=headB;
    
    int count1=0;
    int count2=0;
    while(pcur1)//得到第一个链表的结点数
    {
        count1++;
        pcur1=pcur1->next;
    }
    while(pcur2)//得到第二个链表的节点数
    {
        count2++;
        pcur2=pcur2->next;
    }
    int tmp=abs(count1-count2);//得到两个链表节点数的差值
    ListNode* longlist=headA;
    ListNode* shortlist=headB;
    if(count1<count2)//确定长短链表
    {
        longlist=headB;
        shortlist=headA;
    }
    while(tmp--)//先将长链表走tmp步
    {
        longlist=longlist->next;
    }
    while(longlist && shortlist)//同时遍历两个链表
    {
        if(longlist==shortlist)
        {
            return longlist;
        }
        longlist=longlist->next;
        shortlist=shortlist->next;
    }
    return NULL;
}

2.环形链表I

141. 环形链表 - 力扣(LeetCode)

根据以上的题目描述就可以看出该算法题要我们实现的是判断链表是否为循环链表,在此循环链表是指链表的尾节点不在是连接NULL,而是连接链表中的其中一个节点

那么使用什么方法能判断链表是否为循环链表呢?

在此我们使用的是之前学习过的前后指针法,首先定义有两个指针变量fast和slow,之后fast指针每次走两步,slow指针每次走一步,到最后如果fast指针和slow相遇就说明该链表为循环链表

例如以下示例:

要判断以上链表是否为循环链表我们来看看使用前后指针法的过程是什么样的,首先定义快指针fast和满指针slow,之后让fast和slow遍历链表

 正如上图所示slow指针和fast指针在节点-4相遇,这就说明该链表为循环链表为循环链表

接下来我们就来实现该算法题的代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) 
{
    ListNode* fast,*slow;
    fast=slow=head;
    while(fast && fast->next)
    {
        fast=fast->next->next;
        slow=slow->next;
        if(fast==slow)
        {
            return true;
        }
    }
    return false;
}

在解决了以上的算法题后我们就要思考为什么快指针每次走两步,慢指针走一步可以相遇,有没有可能遇不上呢?

slow一次走⼀步,fast一次走2步,fast先进环,假设slow也走完入环前的距离,准备进环,此时fast和slow之间的距离为N,接下来的追逐过程中,每追击⼀次,他们之间的距离缩小1步

这时在追击过程中fast和slow之间的距离变化:

因此,在带环链表中慢指针走一步,快指针走两步最终一定会相遇。

那么在解决以上问题后思考快指针一次走3步,走4步,...n步行吗?

step1:
按照上面的分析,慢指针每次走一步,快指针每次走三步,此时快慢指针的最大距离为N,接下来的追逐过程中,每追击一次,他们之间的距离缩小2步
追击过程中fast和slow之间的距离变化:

分析:
1、如果N是偶数,第一轮就追上了
2、如果N是奇数,第一轮追不上,快追上,错过了,距离变成-1,即C-1,进入新的一轮追击

   a:C-1如果是偶数,C-1如果是偶数,那么下一轮就追上了
    b:C-1如果是奇数,那么就永远都追不上                          
总结一下追不上的前提条件:
N是奇数,C是偶数

step2:

在以上的step1中我们得出当fast追不上的前提条件:N是奇数,C是偶数,那么接下来我们就要继续分析这种情况是否会出现

假设:
环的周长为C,头结点到slow结点的长度为L,slow走一步,fast走三步,当slow指针入环后,slow和fast指针在环中开始进行追逐,假设此时fast指针已经绕环x周。
在追逐过程中,快慢指针相遇时所走的路径长度:

fast: L+xC+C-N
slow: L

由于慢指针走一步,快指针要走三步,因此得出: 3 * 慢指针路程 = 快指针路程 ,即:

3L=L+xC+C-N
2L=(x-1)C-N

对上述公式继续分析:由于偶数乘以任何数都为偶数,因此 2L 一定为偶数则可推导出可能得情
况:

• 情况1:偶数 = 偶数 - 偶数
• 情况2:偶数 = 奇数 - 奇数

由step1中(1)得出的结论,如果N是偶数,则第一圈快慢指针就相遇了。
由step1中(2)得出的结论,如果N是奇数,则fast指针和slow指针在第一轮的时候套圈了,开始进行下一轮的追逐;当N是奇数,要满足以上的公式,则 (x+1)C 必须也要为奇数,即C为奇数,满足(2)a中的结论,则快慢指针会相遇

因此, step1 中的 N是奇数,C是偶数不成立,既然不存在该情况,则快指针一次走3步最终一定也可以相遇。

因此快指针一次走4、5.....步最终也会相遇,其证明方式同上

注:虽然已经证明了快指针不论走多少步都可以满足在带环链表中相遇,但是在编写代码的时候会有额外的步骤引入,涉及到快慢指针的算法题中通常习惯使用慢指针走⼀步快指针走两步的方式。

3.环形链表II

142. 环形链表 II - 力扣(LeetCode)

在以上环形链表I中我们已经实现了如何判断一个链表是否为环形链表,接下来我们在环形链表算法题中我们将跟进一步当链表是环形链表时我们还要返回链表进入环的第一个节点

那么要如何才能找到环形链表进入环的第一个节点呢?接下来我们要来了解一个性质

让一个指针从链表起始位置开始遍历链表,同时让⼀个指针从判环时相遇点的位置开始绕环运
行,两个指针都是每次均走一步,
最终肯定会在入口点的位置相遇

例如以下示例:

根据使用快慢指针法我们就可以的到以上链表两指针的相遇节点为-4

在此之后我们再定义一个指针变量pcur指向链表的第一个节点,之后让pcur指针和fast指针同时遍历,当这两个指针指向同一个节点时,指针指向的节点就是入环前的节点

接下来我们就来实现该算法题的代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head)
{
    ListNode* fast,*slow;
    fast=slow=head;
    while(fast&& fast->next)
    {
        fast=fast->next->next;
        slow=slow->next;
        if(slow==fast)
        {
            ListNode* pcur=head;
            while(pcur!=fast)
            {
                pcur=pcur->next;
                fast=fast->next;
            }
            return fast;
        }
    }
    return NULL;
}

在解决了以上的算法题后我们就要证明为什么在环形链表中以上的这种性质

在以上图示当中H为链表的起始点,E为环入口点,M与判环时候相遇点 

在此我们设环的长度为R,H到E的距离为L,E到M的距离为 X ,则:M到E的距离为 R-X

由于快指针和满指针走过的路径分别为
fast: L+X + nR
slow:L+X

又因为快指针走过的路径长度为满指针走过的路径长度的两倍,这时就能得到以下的公式
L+X+nR=2(L+X)
根据以上公式就可以推断出以下公式:
L+X=nR
在此可以继续得到

L=(n-1)R+R-X

当慢指针进入环时,快指针可能已经在环中绕了n圈了,n⾄少为1因为:快指针先进环走到M的位置,,最后又在M的位置与慢指针相遇

所以以上公式n可能为1,2,3,4......,n的大小取决于环的大小,环越小n越大,在此极端情况下,假设n=1,此时: L=R-X

根据L=R-X即可说明⼀个指针从链表起始位置运行,⼀个指针从相遇点位置绕环,每次都走⼀步,两个指针最终会在入口点的位置相遇

4.随机链表的复制

138. 随机链表的复制 - 力扣(LeetCode)

根据以上的题目描述可以看出该算法题和我们之前解决的题不同,在该算法题中的链表节点中的有三个成员变量,相比正常的链表节点多了一个随机的指针random。在此题目要我们实现的是链表的拷贝,注意在此不是将原链表内的指针复制一份,这种只是浅拷贝在此算法题中我们要实现的是深拷贝,也就是要额外开一份和原链表一样的内存空间,在将原链表内的数据拷贝给新的链表

接下来我们就来分析如何来实现链表的拷贝

例如以下示例:

 在以上链表中若要实现链表的拷贝,那么该如何实现呢?

在此你可能会想到可以通过遍历原链表,在每遍历到一个节点时就创建一个新的节点,再将原链表节点内的数据拷贝给新节点,之后再将新节点尾插新链表

但是在以上的示例按照这种方法来实现就会发现问题了,在以上示例的链表按照以上的方法来拷贝在第一个节点时没问题,当到了第二个节点就出现一个问题就是原链表第二个节点中的random指针指向的第一个节点,但在使用以上方法时第二个节点中的random无法找到第一个节点,这时因为单向链表中无法向前遍历链表

因此要解决链表的拷贝以上的方法行不通,在此我们需要想其他的方法
接下来就来讲解一种很妙的方法

首先是在原链表基础上复制链表

在此我们先定义两个指针变量pcur和Next分别指向原链表第一个节点和第二个节点

之后创建一个新的节点并且将第一个节点内的值val拷贝到新节点中,再将新节点插入到pcur和Next节点中间,完成以上操作后让pcur指向Next指向的节点,Next指向其next指针指向的节点

之后在遍历原链表每个节点时重复以上的操作,直到pcur指向NULL停止操作,这时原链表就变为以下形式

在以上我们创建的新节点只进行了val的拷贝,那么接下来就来对random来进行拷贝,要进行random的拷贝接下来要再创建一个指针变量copy让其一开始指向创建的第一个新节点,并且之后还要让pcur和Next指针重新回到初始位置

之后继续置random指针 

在此pcur指向节点的random为NULL,就让这时copy指向的节点内的random也置为NULL,之后让pcur指向Next指向的节点,Next指向其next指针的next指向的节点,copy指向改变后的pcur的next指针指向的节点

之后再遍历原链表,当pcur指向节点内的random不为NULL时,让copy内的random指针指向pcur内random指向的节点next指针指向的节点,也就是copy->random=pcur->random->next,最后当pcur为NULL停止,这时原链表就变为以下形式

完成以上操作后最后就要将原链表和赋值链表断开

在此让pcur的next指针指向copy的next指针指向的节点,copy的next指针指向pcur的next指针指向的节点next指针指向的节点,之后让copy和pcur都往后走一步

之后再遍历原链表,重复以上的操作,这时原链表就变为以下形式

 

通过以上示例的讲解就可以发现链表的复制分为以下3步:

接下来就来实现该算法题的代码

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

typedef struct Node Node;
 Node* NewNode(int x)
 {
    Node* newnode=(Node*)malloc(sizeof(Node));
    newnode->val=x;
    newnode->next=newnode->random=NULL;
    return newnode;
 }

struct Node* copyRandomList(struct Node* head)
{
    if(head==NULL)
    {
        return head;
    }
    Node* pcur=head;
//在原链表基础上复制链表
    while(pcur)
    {
        Node* Next=pcur->next;
        Node*newnode=NewNode(pcur->val);
        newnode->next=Next;
        pcur->next=newnode;

        pcur=pcur->next->next;
    }
//置random指针
    pcur=head;
    while(pcur!=NULL)
    {
        Node* newpcur=pcur->next;
        if(pcur->random!=NULL)
        {
         newpcur->random=pcur->random->next;
        }
        pcur=newpcur->next;
    }
//将原链表和赋值链表断开
    pcur=head;
    Node* newhead,*newptail;
    newhead=newptail=pcur->next;
    while(pcur->next->next)
    {
       pcur=pcur->next->next;
       newptail->next=pcur->next;
       newptail=newptail->next;
    }
	return newhead;
}

以上就是链表算法题的全部内容了,希望能得到你的点赞、收藏

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

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

相关文章

基于yolov8的人脸检测计数系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv8的人脸检测计数系统是一种利用深度学习技术的先进解决方案&#xff0c;它以其高效、准确的特点在人脸检测领域脱颖而出。该系统以YOLOv8为核心算法&#xff0c;该算法作为YOLO系列的最新迭代&#xff0c;不仅继承了前代算法的优点&#xff0c;还在实时…

YOLOv8-obb训练自己的数据集

一、YOLO OBB 格式 YOLO OBB 格式通过四个角点指定边界框&#xff0c;其坐标在 0 和 1 之间归一化&#xff1a; class_index x1 y1 x2 y2 x3 y3 x4 y4 YOLO 在内部处理损失和产出。 xywhr 格式&#xff0c;表示边界框的中心点&#xff08;xy&#xff09;、宽度、高度和旋转角度…

树莓派5_opencv笔记27:Opencv录制视频(无声音)

今日继续学习树莓派5 8G&#xff1a;&#xff08;Raspberry Pi&#xff0c;简称RPi或RasPi&#xff09; 本人所用树莓派5 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: Opencv 与 python 版本如下&#xff1a; 今天就水一篇文章&#xff0c;用树莓派摄像头&…

NISP 一级 | 2.4 访问控制

关注这个证书的其他相关笔记&#xff1a;NISP 一级 —— 考证笔记合集-CSDN博客 0x01&#xff1a;访问控制基本概念 访问控制是针对越权使用资源的防御措施。 其目标是防止对任何资源&#xff08;如计算资源、通信资源或信息资源&#xff09;进行未授权的访问&#xff0c;从而…

BLIP3技术小结(xGen-MM (BLIP-3): A Family of Open Large Multimodal Models)

paperhttps://www.arxiv.org/abs/2408.08872githubhttps://github.com/salesforce/LAVIS/tree/xgen-mmOrg.Salesforce AI Research个人博客地址http://myhz0606.com/article/blip3 前置阅读&#xff1a;BLIP系列总结 核心思路 虽然过去BLIP系列对LMM发展起到至关重要的作用&…

Redis缓存常用的读写策略

缓存常用的读写策略 缓存与DB的数据不一致问题&#xff0c;大多数都是指DB的数据已经修改&#xff0c;而缓存中的数据还是旧数据的情况。 旁路缓存模式 对于读操作&#xff1a;基本上所有模式都是先尝试从缓存中读&#xff0c;没有的话再去DB读取&#xff0c;然后写到缓存中…

MSCKF7讲:特征管理与优化

MSCKF7讲&#xff1a;特征管理与优化 文章目录 MSCKF7讲&#xff1a;特征管理与优化1 Feature.h2 OptimizationConfig3 initializePosition三角化LM优化3.1 计算归一化坐标深度初值generateInitialGuess① 理论推导② 代码分析 3.2 计算归一化误差cost① 理论推导② 代码分析 3…

模型和算力看板:Compute DashBoard

AGI 之路 AGI&#xff08;通用人工智能&#xff09;是整个计算机科学的圣杯&#xff0c;算力的增长和模型能力的提升&#xff08;算法和数据&#xff09;缺一不可。作为一个新质生产力&#xff0c;构建一个合理的评价体系是常用的方法论。针对模型和算力的评价&#xff0c;有类…

【AutoX.js】选择器 UiSelector

文章目录 原文&#xff1a;https://blog.c12th.cn/archives/37.html选择器 UiSelector笔记直接分析层次分析代码分析 最后 原文&#xff1a;https://blog.c12th.cn/archives/37.html 选择器 UiSelector 笔记 AutoX.js UiSelector 直接分析 用于简单、最直接的查找控件 开启悬…

code agent-自动写代码的AI工具整理(2024)

文章目录 cursorReplit Agentgithub-copilot cursor https://www.cursor.com/ 界面类似VSCODE&#xff0c;集成多种先进功能的AI驱动的代码编辑器&#xff0c;智能代码编辑与补全、使用自然语言指令来编辑和修改代码、版本控制与代码审查、自动化测试等&#xff1b; Replit…

路径规划——RRT算法

路径规划——RRT算法 算法原理 RRT算法的全称是快速扩展随机树算法(Rapidly Exploring Random Tree)&#xff0c;它的思想是选取一个初始点作为根节点&#xff0c;通过随机采样&#xff0c;增加叶子节点的方式&#xff0c;生成一个随机扩展树&#xff0c;当随机树中的叶子节点…

基于阿里云函数计算(FC)x 云原生 API 网关构建生产级别 LLM Chat 应用方案最佳实践

作者&#xff1a;计缘 LLM Chat 应用大家应该都不陌生&#xff0c;这类应用也逐渐称为了我们日常的得力助手&#xff0c;如果只是个人使用&#xff0c;那么目前市面上有很多方案可以快速的构建出一个LLM Chat应用&#xff0c;但是如果要用在企业生产级别的项目中&#xff0c;那…

开源软件如何保证数据安全?从一下七个方面进行分析

1、加密软件来保护 在开源软件中&#xff0c;数据加密和保护是保障数据安全的重要手段。开发者应该使用强加密算法的加密软件来保护敏感数据&#xff0c;并确保数据传输和存储的安全性。通过驱动层加密技术&#xff0c;对开发源代码进行强制加密处理&#xff0c;文件创建自动进…

Mac在Python项目中通过opencv模版匹配定位不到图片

起因 原本一行代码的事情&#xff0c;但是在Mac上总能出现意外&#xff0c;如下 box pyautogui.locateOnScreen(obsidian.png) print(box) pyautogui.moveTo(box[0],box[1])上面的代码用来定位图片在屏幕中的位置&#xff0c;然后移动鼠标到定位到的屏幕位置坐标。 意外…

IT管理:我与IT的故事9-数字化转型7步思考行动法

四书即论语、孟子、大学、中庸&#xff0c;又称四子书&#xff0c;为历代儒学子首要研习之书。南宋朱熹取《礼记》之大学、中庸篇&#xff0c;分章注释&#xff0c;与论语、孟子合为“四书”。 四书及其注释包涵孔子弟子及再传弟子、孟子、程子、朱熹等&#xff0c;其编撰时长达…

代码随想录Day 38|背包问题完结,题目322.零钱兑换、279.完全平方数、139,单词拆分数

提示&#xff1a;DDU&#xff0c;供自己复习使用。欢迎大家前来讨论~ 文章目录 动态规划part06题目题目一&#xff1a;322. 零钱兑换解题思路&#xff1a;题目二&#xff1a;279.完全平方数题目三&#xff1a;139.单词拆分数解题思路&#xff1a;背包问题 多重背包&#xff08;…

基于 YOLOv5 的积水检测系统:打造高效智能的智慧城市应用

在城市发展中&#xff0c;积水问题日益严重&#xff0c;特别是在大雨过后&#xff0c;积水往往会影响交通甚至威胁人们的安全。通过现代计算机视觉技术&#xff0c;我们能够智能化地检测和识别积水区域&#xff0c;减少潜在危险。本文将介绍如何使用 YOLOv5 和 PyQt5 搭建一个积…

Unity使用自定义类型作为字典Key的最佳实践与避坑指南

自定义类型作为字典Key的最佳实践与避坑指南文章首发 问题背景 首先提一下之前项目开发时遇到的一个将自定义类型作为Dictionary键的坑。 项目中&#xff0c;我们有两个业务类BusinessA和BusinessB&#xff0c;因为某个需求&#xff0c;我们需要将这两个类建立一个映射关系&…

游泳馆收银系统源码解析之手牌管理--SAAS本地化及未来之窗行业应用跨平台架构

一、代码 if(手牌状态 "空"){结算界面 "";未来之窗_人工智能_通用页面_尺寸(title"游泳馆",收费,500,300);}else{未来之窗_人工智能_通用页面_尺寸(title"游泳馆",退款,1200,500);} 二、阿雪技术观 拥抱开源与共享&#xff0c;见…

探索图论中的关键算法(Java 实现)

“日出东海落西山 愁也一天 喜也一天 遇事不钻牛角尖” 文章目录 前言文章有误敬请斧正 不胜感恩&#xff01;||Day031. 最短路径算法Dijkstra算法Java 实现&#xff1a; Bellman-Ford算法Java 实现&#xff1a; 2. 最小生成树算法Prim算法Java 实现&#xff1a; Kruskal算法Ja…