常见的链表的OJ题

news/2024/5/19 23:24:13/文章来源:https://blog.csdn.net/lmbuhuiku/article/details/130367544

  在本次的博客当中,为了巩固关于链表技能的运用,我们先来看一些与链表有关的OJ题。

    🌵反转链表

   题目详情如下:

     第一道题目从逻辑上看不难,我们只需要将链表进行拆分,将我们下一个节点进行一个类似于头插的操作即可。需要我们注意的是我们需要创建一个指针变量接收我们的下一个节点的地址之后才可以进行节点下一个位置的覆盖,否则就会造成节点的遗失。

  所进行编写的代码如下:

struct ListNode* reverseList(struct ListNode* head)
{if(head==NULL)return head;if(head->next==NULL)return head;//接收头节点的指针struct ListNode*next=head->next;//接收拆下链表的节点struct ListNode*prev=head;//struct ListNode*ret=next;head->next=NULL;while(next){next=next->next;ret->next=prev;prev=ret;if(next!=NULL){ret=next; }}return ret;
}

    🌵链表的中间节点

  本题目需要我们使用一些小技巧,比如说快慢指针,我们需要设置两个指针,均从链表的头部开始。一个慢指针一次走一步,一个快指针一次走两步,我们最后得到的就是链表的中间节点。(当我们链表当中的数据为奇数个的时候,满指针所指向的是中间的节点,快指针指向的是中间靠后的一个节点)

   就像是我们上面的构思图所示,我们只需要判断我们fast节点的下一位是否为NULL就可以判断链表是否已经遍历结束。但是我们会发现当我们的链表节点个数为偶数的时候fast指针最后会指向NULL,如果这个时候将NULL作为地址判断next的话就会产生空指针的非法使用的问题。我们可以分开判断一次判断fast即可。所示的代码如下:

struct ListNode* middleNode(struct ListNode* head)
{struct ListNode*before=head;struct ListNode*next=head;while(next){next=next->next;if(next==NULL){return before;}else{next=next->next;}before=before->next;}return before;
}

    🌵链表倒数第k个结点 

  这道题看起来很难可是思路却和我们判断链表的中间节点很类似。我们同样需要设置一个前后指针。我们想要寻找倒数第k个节点就可以先让一个指针走k-1步之后让另一个指针从开始的头节点开始走,最后当先走的那个指针指向NULL的时候,我们后走的指针得到的就是倒数第K个位置的指针。

   我们循环的结束条件可以设置为我们的first指针的next为NULL。按照我们上述思路可以书写出以下的代码:

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) 
{// write code here//判断节点为空if(pListHead==NULL){return NULL;}struct ListNode*behind=pListHead;struct ListNode*next=pListHead;//后节点先走k-1步k--;while(k--){if(next->next==NULL){return NULL;}next=next->next;}while(next->next!=NULL){behind=behind->next;next=next->next;}return behind;
}

    🌵合并两个有序链表

 

   想要合并两个链表其实很简单我们只需要将我们两个链表进行分别的拆分,之后比较大小,将较小的一方拼接入新的链表当中。(为了是我们节点拼接更加方便,我们可以后才能构建一个头节点,将我们查下来的节点链接进入我们头节点的后面即可)所示代码如下:

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{struct ListNode* ret1 = list1;struct ListNode* ret2 = list2;struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* first = newnode;while (ret1 && ret2){if (ret1->val >= ret2->val){newnode->next = ret2;ret2 = ret2->next;newnode = newnode->next;}else{newnode->next = ret1;ret1 = ret1->next;newnode = newnode->next;}}//将另一个没有处理完的链表链入我们的合并链表当中if (ret1 == NULL){newnode->next = ret2;}else{newnode->next = ret1;}struct ListNode* ret = first->next;free(first);first = NULL;return ret;
}

    🌵 链表分割

 

   阅读我们题目中的要求我们可以知道,我们需要进行的操作就是将我们链表当中的数据进行分裂,大于等于特定值的归为一类,小于特定值的归为另一类。我们可以创建两个节点,将我们较小的放入lower节点后面,将我们较大的放入bigger节点后面。之后再将lower和bigger两个链表拼接即可得到我们目标的链表。

    经过上面的梳理我们可以写出以下的代码:

ListNode* partition(ListNode* pHead, int x) {// write code here//开辟两个新的结构体变量//将我们的大于或者小于定值的节点放到指定位置后面struct ListNode*bigger=(struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode*lower=(struct ListNode*)malloc(sizeof(struct ListNode));//创建三个结构体指针变量//分别作为我们遍历链表,后面释放开辟好的节点进行使用struct ListNode*HeadBigger=bigger;struct ListNode*HeadLower=lower;struct ListNode*ret=pHead;//开始遍历链表while(ret){if(ret->val<x){lower->next=ret;lower=lower->next;}else {bigger->next=ret;bigger=bigger->next;}ret=ret->next;}lower->next=HeadBigger->next;bigger->next=NULL;struct ListNode*retu=HeadLower->next;free(HeadBigger);free(HeadLower);return retu;}

    🌵 链表的回文结构

 

  要是学过栈结构的朋友们一看到这个题目都会想到,我们可以使用栈呀!就和我们括号的判断一样。但是对于栈我们还没有进行讲解,所以我们先来使用一些技巧进行进行本道题目的讲解:

  要想判断我们的链表是否为回文结构,我们只需要从中间节点开始判断即可。因为假如符合我们的回文结构的话前面的节点的内容和我们后面节点当中的内容是完全逆置的。所以对于本题我们需要进行的操作为:先找到来链表当中的中间节点,之后将我们的后半部分链表进行逆序,最后在判断我们前后链表的结构是否相同(可以剩下一个节点不做判断,即当我们节点的个数为奇数个时,我们只需要知道一个链表为空即为链表的遍历结束。)

  所编写的代码如下:

struct ListNode* reverseList(struct ListNode* head){if(head==nullptr)return head;if(head->next==nullptr)return head;//接收头节点的指针struct ListNode*next=head->next;//接收拆下链表的节点struct ListNode*prev=head;//struct ListNode*ret=next;head->next=nullptr;while(next){next=next->next;ret->next=prev;prev=ret;if(next!=nullptr){ret=next; }}return ret;}struct ListNode* middleNode(struct ListNode* head){struct ListNode*before=head;struct ListNode*next=head;while(next){next=next->next;if(next==nullptr){return before;}else{next=next->next;}before=before->next;}return before;}bool chkPalindrome(ListNode* phead)
{// write code hereListNode* mid = middleNode(phead);ListNode* rmid = reverseList(mid);while (phead && rmid){if (phead->val != rmid->val){return false;}phead = phead->next;rmid = rmid->next;}return true;
}

  其中的查找中间节点的函数和逆序函数复用我们之前实现好的即可。

    🌵 相交链表

  这道题相比于我们之前的题目来说简单很多。我们观察一下相交链表的特点我们会发现:我们的链表一旦相交那么最后一个节点的地址一定相同,否则最后一个节点的值就不同。我们可以利用这一特点很简单的编写我们的代码。我们可以对这两个链表分别进行遍历,找到最后的节点,然后判断两个节点的地址是否相同即可。所示代码如下:

struct ListNode* getIntersectionNode(struct ListNode* headA, struct ListNode* headB)
{//count代表的是两个链表的节点个数之差struct ListNode* s1 = headA;struct ListNode* s2 = headB;while (s1->next){s1 = s1->next;}while (s2->next){s2 = s2->next;}if (s1 != s2){return NULL;}int count = 0;struct ListNode* ret1 = headA;struct ListNode* tmp1 = headA;struct ListNode* ret2 = headB;struct ListNode* tmp2 = headB;while (ret1 && ret2){ret1 = ret1->next;ret2 = ret2->next;}if (ret1 == NULL){while (ret2){ret2 = ret2->next;count++;}while (count--){tmp2 = tmp2->next;}}else{while (ret1){ret1 = ret1->next;count++;}while (count--){tmp1 = tmp1->next;}}while (tmp1 != tmp2){tmp1 = tmp1->next;tmp2 = tmp2->next;}return tmp1;
}

    🌵 环形链表

   链表带环问题其实就是一个追击相遇问题。我们只需要使用我们前面说到过的快慢指针的方法即可。 我们可以让我们的一个指针每次先走一步,另一个指针每次走两步。如果链表带环的话那么我们的快指针最后一定会追上我们的慢指针。我们利用上述的思路可以编写如下的代码:

bool hasCycle(struct ListNode* head)
{if (head == NULL){return false;}if (head->next == head){return true;}struct ListNode* before = head;struct ListNode* behind = head;while (behind){before = before->next;behind = behind->next;if (behind == NULL){return false;}else{behind = behind->next;}if (behind == before){return true;}}return false;
}

    🌵 循环链表2

  我们本道链表带环问题可以说是我们上一道题目的plus版。我们不仅需要判断链表是否带环,还需要返回我们刚进入环内的节点。对于本题我们可以采用一些数学上的逻辑来进行推断:

   经过我们上面的推导之后我们就得出了n*C-N=L,也就是说我们从开始到循环节点的路程等于我们走整圈的数量减去我们从开始循环节点到我们相遇节点的距离。那么假如我们提前将这个N走完了,也就是说只要我们从相遇节点出发两个指针一旦相遇那么相遇节点一定就是我们进入循环的节点了呢?根据我们上面所推得的结论我们可以编写以下代码:

struct ListNode *detectCycle(struct ListNode *head) 
{//判断相遇的节点struct ListNode*fast=head;struct ListNode*slow=head;struct ListNode*meetnode=NULL;struct ListNode*retu=NULL;if(head==NULL){return NULL;}while(fast&&slow){fast=fast->next;if(fast==NULL){return NULL;}else{fast=fast->next;}slow=slow->next;if(fast==slow){meetnode=fast;break;}}//一个节点从头开始走,一个节点从相遇节点开始走,最后在循环节点相遇struct ListNode*begin=head;if(head==meetnode){return head;}while(begin&&meetnode){begin=begin->next;meetnode=meetnode->next;if(begin==meetnode){retu=begin;return begin;}}return NULL;
}

    🌵复制带随机指针的链表

  最后两道题可能是我们本次博客最难的两道题目了,只要掌握好了这两道题相信我们对于链表的理解已经很深刻了。接下来我们来攻破最后一道难关。

  对于这一道题目如果没有一个清晰的思路我们会感到很头疼,接下来我来向大家介绍一种思路:
我们想要复制上面的代码可以尝试着创建相等数量的节点,最后将我们每一个节点链接到我们相应节点的后面,之后将我们的random指针指向我们原链表当中节点的下一个即可。

   唯一需要我们特别注意的是我们需要特别判断我们的random指向null的情况,因为指向空在进行next引用的话就会产生空指针的非法引用的问题。

  当我们来链接好我们的新建链表之后我们只需要在再进行链表拆分,拆下我们拼接上的节点即可。经过我们上面的思路我们可以编写如下的代码:

struct Node* BuyNewNode(int data)
{struct Node* newnode = (struct Node*)malloc(sizeof(struct Node));newnode->val = data;newnode->next = NULL;return  newnode;
}struct Node* copyRandomList(struct Node* head) {//在每一个节点的后面创建一个新的节点,用于生成新的链表if (head == NULL){return NULL;}if (head->next == NULL){struct Node* ret1 = (struct Node*)malloc(sizeof(struct Node));if(head->random==NULL){ret1->random=NULL;}else{ret1->random=ret1;}ret1->val = head->val;ret1->next = head->next;return ret1;}struct Node* tmp = head;while (tmp){struct Node* newnode = BuyNewNode(tmp->val);//保存链表当中下一个节点的地址struct Node* ret = tmp->next;tmp->next = newnode;newnode->next = ret;tmp = ret;}//将节点中的随机值复制进入新的节点tmp = head;while (tmp){if (tmp->random==NULL){tmp->next->random = NULL;}else{tmp->next->random = tmp->random->next;}tmp = tmp->next->next;}//将创建好的新的链表进行拆解//跳过原链表当中的节点tmp = head->next;struct Node* retu = head->next;struct Node* newlist = tmp;while (tmp){newlist->next = newlist->next->next;tmp = newlist->next->next;newlist = newlist->next;}newlist->next = NULL;return retu;
}

   以上就是我们本次博客的主要内容了,感谢您的观看,再见。

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

无良公司把我从上家挖过来,白嫖了六个月,临近试用期结束才说不合适,催我赶紧找下家!...

职场套路多&#xff0c;一不小心就会掉坑&#xff0c;一位网友讲述了自己的遭遇&#xff1a; 今天被领导催促离职了&#xff0c;当时就是这个领导把他从别的公司挖过来。这家公司催得太急&#xff0c;为了投奔这里&#xff0c;他和上家的HR都闹翻了&#xff0c;上家总监挽留他&…

【信息安全案例】——身份与访问安全(学习笔记)

&#x1f4d6; 前言&#xff1a;一位用户对计算机信息资源的访问活动中&#xff0c;首先必须拥有身份标识&#xff0c;通过该标识鉴别该用户的身份&#xff0c;进一步地&#xff0c;用户还应当具有执行所请求动作的必要权限&#xff0c;系统会验证并控制其能否执行对资源试图完…

31-基于GA遗传算法的车辆充电调度系统优化matlab程序

资源地址&#xff1a; 主要内容&#xff1a; 研究多辆电动汽车的充电调度问题&#xff0c;考虑某时段区域范围内有M 辆电动汽车发出充电请求时&#xff0c;周围有N 个充电桩可以提供充电位的调度情况。把当前调度时段电动汽车和充电桩的基本数据加载到调度中心&#xff0c;调度…

华为OD机试真题(Java),旋转数组的最小数字(100%通过+复盘思路)

一、题目描述 有一个长度为 n 的非降序数组&#xff0c;比如[1,2,3,4,5]&#xff0c;将它进行旋转&#xff0c;即把一个数组最开始的若干个元素搬到数组的末尾&#xff0c;变成一个旋转数组&#xff0c;比如变成了[3,4,5,1,2]&#xff0c;或者[4,5,1,2,3]这样的。请问&#xf…

Linux系列讲解 —— SSH登录

讲解一下ssh远程登陆的基础知识。 目录 0. 基本原理1. 安装ssh程序&#xff1a;1.1 windows平台(Win10)1.2 Linux平台(Ubuntu18.04) 2. 密码方式远程登录3. 密钥方式远程登录3.1 生成私钥公钥对3.2 将公钥复制到远程机器3.3 尝试ssh远程登录 4. 常见问题4.1 sun192.168.1.21: P…

51单片机(四)静态数码管和动态数码管显示

❤️ 专栏简介&#xff1a;本专栏记录了从零学习单片机的过程&#xff0c;其中包括51单片机和STM32单片机两部分&#xff1b;建议先学习51单片机&#xff0c;其是STM32等高级单片机的基础&#xff1b;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 &#xff1a;适用于想要…

HOG+SVM分类器实践

文章目录 HOGSVM分类器实践制作SVM分类器导入所需的库提取HOG特征读取正样本和负样本训练分类器定义主函数小结 测试SVM分类器相关疑问1. 提取HOG特征为什么不能彩色图像呢&#xff1f;2. 出现如下错误3. 测试代码中&#xff0c;当我传入100*100的图片时候&#xff0c;为什么im…

测试开发实战项目 | 搭建Pytest接口自动化框架

一、预研背景 目前系统研发多为前后端分离&#xff0c;当后端接口研发完成后&#xff0c;可以不依赖前端界面通过接口测试提前发现问题并解决。同时由于软件迭代周期不断缩短&#xff0c;开发新功能后又担心影响原有功能&#xff0c;可以通过接口自动化进行原有功能快速回归测…

git版本本地远程分支管理测试

只为搞清楚一些基本的git的本地提交、分支&#xff0c;远程分支的概念。 创建git库。 在本地首次建立一个001文件&#xff0c;首次提交到本地master&#xff0c;不提交&#xff08;push&#xff09;到远程master&#xff08;gitee&#xff09;。 add 增加001文件到库。 Git-co…

传统协议大比拼之IP协议

文章目录 一、引言二、IP协议的基本特点2.1 IP协议的作用和基本功能2.2 地址管理手动分配IP动态主机配置协议(DHCP)网络地址转换(NAT)IPv6 2.2 路由选择RIP(距离向量型的协议)OSPF(链路状态类型协议)BGP(边界网关协议) 2.3 IP协议的特点&#xff1a; 三、IP地址的组成3.1 IP地址…

三菱GX Works2梯形图程序分段显示设置的具体方法示例

三菱GX Works2梯形图程序分段显示设置的具体方法示例 大家平时在使用GX Works2进行梯形图程序编辑时,默认是一整段在一起,程序步数较多时查看起来不是那么方便,下面就和大家分享如何通过声明编辑来实现程序分段显示。 具体方法可参考以下内容: 如下图所示,打开GX Works2编…

知网导入EndNote

首先进入知网&#xff0c;搜索你想要找的期刊论文。 选择EndNote 点击导出 浏览器自动下载以txt为后缀的文件 导入到EndNote中

汇编小程序解析--3D立方体旋转

汇编小程序解析–3D立方体旋转&#xff0c;源代码如下&#xff0c;是vulture大神于1995年写的&#xff0c;我到现在才基本看懂。 ;本程序由国外的Vulture大哥编写&#xff0c;并公布了源码&#xff0c;这个是他95年的一个作品&#xff0c;可以说是在当时是非常成功的&#xff…

HJL-93/A数字式交流三相电流继电器 导轨安装 约瑟JOSEF

品牌&#xff1a;JOSEF约瑟名称&#xff1a;数字式交流三相电流继电器型号&#xff1a;HJL系列功率消耗&#xff1a;≤5W触点容量&#xff1a;250V/5A额定电压&#xff1a;58、100、110、220V HJL系列 数字式交流三相电流继电器型号&#xff1a; HJL-93/AY数字式交流三相电流继…

博途1200/1500PLC工艺PID编程应用(SCL语言)

博途工艺PID的详细解读可以查看下面的博客文章,这里不再赘述 博途PLC 1200/1500PLC 工艺对象PID PID_Compact详细解读_RXXW_Dor的博客-CSDN博客这篇博文我们详细解读博途PLC自带的PID功能块PID_Compact,大部分工业闭环调节过程,我们采用系统自带的PID功能块基本都能胜任,一…

力扣sql中等篇练习(十三)

力扣sql中等篇练习(十三) 1 每位学生的最高成绩 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 #先找到最大的元素 然后分组即可,不用管某些字段(grade)是不是聚合字段 SELECT e1.student_id,min(e1.course_id) course_id,e1.grade FROM Enrollment…

很不错的一篇文章,值得点赞收藏,带你全面了解MySQL性能调优、错误代码总结和全局参数配置(持续更新中ing)

前言 MySQL是一个关系型数据库管理系统&#xff0c;由瑞典MySQL AB 公司开发&#xff0c;属于 Oracle 旗下产品。MySQL是最流行的关系型数据库管理系统之一&#xff0c;在 WEB 应用方面&#xff0c;MySQL是最好的 RDBMS (Relational Database Management System&#xff0c;关系…

leetcode刷题(9)二叉树(3)

各位朋友们&#xff0c;提前祝大家五一劳动节快乐啊&#xff01;&#xff01;&#xff01;今天我为大家分享的是关于leetcode刷题二叉树相关的第三篇我文章&#xff0c;让我们一起来看看吧。 文章目录 1.二叉树的层序遍历题目要求做题思路代码实现 2.从前序与中序遍历序列构造二…

上海亚商投顾:沪指全天震荡微跌 新能源赛道股集体反弹

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪 大小指数今日走势分化&#xff0c;沪指探底回升小幅下跌&#xff0c;创业板指盘中涨超2%&#xff0c;午后涨幅有所…

黑盒测试过程中【测试方法】详解5-输入域,输出域,猜错法

在黑盒测试过程中&#xff0c;有9种常用的方法&#xff1a;1.等价类划分 2.边界值分析 3.判定表法 4.正交实验法 5.流程图分析 6.因果图法 7.输入域覆盖法 8.输出域覆盖法 9.猜错法 黑盒测试过程中【测试方法】讲解1-等价类&#xff0c;边界值&#xff0c;判定表_朝一…