当前位置: 首页 > news >正文

数据结构——单链表OJ题(下)

目录

一、链表的回文结构

思路一:数组法

(1)注意

(2)解题

思路二:反转链表法

(1) 注意

(2)解题

二、相交链表

(1)思路:快慢指针

(2)注意

(3)解题

 三、环形链表1

(1)思路:快慢指针

(2)解释

(3)注意

(4)解题

①快指针走两步

② 快指针走三步

四、环形链表2

(1)思路:快慢指针

(2)解释

(3)注意

(4)解题

五、随机链表的复制

(1)思路

(2)解题

六、写在最后 


一、链表的回文结构

思路一:数组法

将链表中结点的数据存储在数组中,创建两个指针分别从左右两边遍历并比较,如果左右对称则说明是回文结构。

(1)注意

①为什么要创建数组存储数据?因为链表的结点不是连续的,不能进行逆向访问;

②从左右两端进行遍历时,需满足left<right的条件;

③该方法只适用于链表长度小于等于900的条件下,因为数组的长度最大为900。

(2)解题

typedef struct ListNode ListNode;
bool chkPalindrome(ListNode* A) {//创建数组存储数据int arr[900] = {0};//数据的个数为iint i = 0;ListNode* pcur = A;//遍历链表,将数据存储在数组中while(pcur){arr[i++] = pcur->val;pcur = pcur->next;}int left = 0;int right = i - 1;while(left < right){//若有不相等,说明不是回文结构if(arr[left] != arr[right]){return false;}left++;right--;}//跳出循环,说明left和right指向的数据一直相等,即回文结构return true;}

思路二:反转链表法

使用快慢指针法找到原链表的中间结点,将中间结点之后的链表进行反转,最后将原链表的前半段与新链表存储的数据进行比较。

(1) 注意

①寻找中间结点和反转链表在上节已练习,可将它们封装成两个函数;

②由于原链表的长度大于新链表(反转后的),因此循环结束的判定条件是p2==NULL。

(2)解题

class PalindromeList {//寻找中间结点的函数ListNode* midNode(ListNode* head){ListNode* fast = head;ListNode* slow = head;while(fast && fast->next){fast = fast->next->next;slow = slow->next;}return slow;}//将链表进行反转的函数ListNode* reverse(ListNode* head){ListNode* n1, * n2 , *n3;n1 = NULL;n2 = head;n3 = head->next;while(n2){n2->next = n1;n1 = n2;n2 = n3;if(n3){n3 = n3->next;}}return n1;}
public:bool chkPalindrome(ListNode* A) {//原链表的中间结点ListNode* mid = midNode(A);//将中间结点之后的链表进行反转ListNode* p2 = reverse(mid);ListNode* p1 = A;//遍历两个链表的值,比较是否相等while(p2){//存在不相等的,说明不是回文结构if(p1->val != p2->val){return false;}p1 = p1->next;p2 = p2->next;}//跳出循环,说明left和right指向的数据都相等,即为回文结构return true;}
};

二、相交链表

(1)思路:快慢指针

首先找到两个链表长度的差值k,创建两个指针指向头结点,快指针先走k步,接着快慢指针一起走,若快慢指针指向同一个结点,则说明该结点为相交结点。

(2)注意

①计算差值时无法确定两个长度谁大谁小,可使用abs()绝对值函数;

②通过比较两个长度的大小来确定快指针指向哪个链表的头结点;

③判断快慢指针指向的结点是否相同,而非结点指向的数据!!

(3)解题

typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) 
{//计算两个链表的长度int len1 = 0;int len2 = 0;ListNode* pcur = headA;while(pcur){pcur = pcur->next;len1++;}pcur = headB;while(pcur){pcur = pcur->next;len2++;}//两个链表长度的差值kint k = abs(len1 - len2);//快指针指向长链表的头结点ListNode* fast = headA;ListNode* slow = headB;if(len1 < len2){fast = headB;slow = headA;}//快指针先走k步while(k--){fast = fast->next;}//比较两个指针指向的结点是否相同while(fast && slow){if(fast == slow){return fast;}fast = fast->next;slow = slow->next;}return NULL;
}

 三、环形链表1

(1)思路:快慢指针

创建快慢指针并指向头结点,快指针一次经过两个结点,慢指针一次经过一个结点,若最终快指针与慢指针相遇,则说明该链表是环形链表。

(2)解释

①为什么在起点相同的情况下,快指针走两步,慢指针走一步,两者可以相遇,会不会遇不上?

假设此时fast进入环,slow走完入环前的距离准备入环,此时它们之间的距离为N。在接下来的追逐中,它们每追击(变换位置)一次,两者之间的距离缩小一步,直至相遇。(参考下图理解)

 

②如果慢指针一次走一步,快指针一次走3、4、5...n步,快慢指针还能再相遇吗?

可以!以快指针一次走3步为例:

slow入环后,slow和fast在环内进行追逐,假设两者之间的距离为N,那么在之后的过程中,每追逐一次两者之间的距离就缩小两步。

若N为偶数,最终距离可缩小为0,即相遇;若N为奇数,假设环的长度为C,会出现套圈情况(即slow走在fast前面),此时两者之间的长度为C-1。

此时需要讨论C-1是奇数还是偶数,若C-1为偶数,则可以相遇。若C-1为奇数,两者还会错过,继续套圈,那么fast和slow还会再相遇吗?

假设slow走完入环前的距离L,刚准备入环;fast在环里走了x圈,且此时fast和slow之间的距离为N,那么两者走过的长度分别为:slow : L     fast : L + xC + (C - N)。又因为fast的速度是slow的3倍,因此3L =  L + xC + (C - N),化简得:2L = (x + 1)C - N

上述条件中:N为奇数,C为偶数。因为:偶数= 偶数-偶数,偶数=奇数-奇数。那么(x+1)C为奇数时,会相遇。因此,如果慢指针一次走一步,快指针一次走3步,快慢指针还能再相遇。

同理可得其它步数的情况下也可相遇。

(3)注意

①循环结束的判定条件是fast && fast->next:若fast为空则说明不是环形链表;

②不能写成fast->next && fast,要先判断fast,因为如果fast为空,就不能取next;

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

(4)解题

①快指针走两步
typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) 
{ListNode* fast = head;ListNode* slow = head;while(fast && fast->next){fast = fast->next->next;slow = slow->next;if(fast == slow){return true;}}return false;
}
② 快指针走三步
typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) 
{ListNode* fast = head;ListNode* slow = head;while(fast && fast->next){slow = slow->next;//快指针一次走三步int n = 3;while(n--){if(fast->next){fast = fast->next;}else {return false;}}if(fast == slow){return true;}}return false;
}

四、环形链表2

(1)思路:快慢指针

创建两个指针,一个从链表的头结点开始遍历链表,另一个从快慢指针的(判断是否为环形链表的)相遇点开始绕环运行,两个指针都是一次走一步,最终会在入口点的位置相遇。

(2)解释

①为什么相遇点和头结点到入环结点的距离是相等的?

假设环的长度为R,入环之前的距离为L,入环节点到相遇点的距离为X,那么相遇点到入环结点之间的距离为R-X。

在判环时(此时在相遇点),快慢指针走的路程为:slow : L+X     fast :L+nR+X(其中n≥1)。

由于判环时,快指针一次走两步,慢指针一次走一步,则快指针的路程时慢指针的两倍。且慢指针入环之后,快指针一定会在一圈之内追上慢指针(因为慢指针入环之后,两者之间的距离最大为环的长度,而每追击一次,两者之间的距离缩短一步,因此在一圈之内,快指针一定能追上慢指针)。

那么:2*(L+X)= L+nR+X,化简为:L = nR - X,即L = (n -1)R+(R-X)。在极端情况下:假设n=1,则:L=R-X,即头结点到和相遇点到入环结点之间的距离相等。

(3)注意

①fast && fast->next位置的注意事项同上。

(4)解题

typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) 
{ListNode *fast = head;ListNode *slow = head;while(fast && fast->next){//fast一次走两步,slow一次走一步fast = fast->next->next;slow = slow->next;if(fast == slow){ListNode* pcur = head;//判断两者何时相遇while(pcur != slow){pcur = pcur->next;slow = slow->next;}return slow;}}//链表不带环return NULL;
}

五、随机链表的复制

 

(1)思路

在原链表的基础上继续复制链表,置random指针(copy->random = pcur->random->next),最后将复制链表和原链表断开,得到复制链表。

(2)解题

typedef struct Node Node;
Node* buyNode(int x) {Node* newnode = (Node*)malloc(sizeof(Node));newnode->val = x;newnode->next = newnode->random = NULL;return newnode;
}
void AddNode(Node* phead) {Node* pcur = phead;while (pcur) {// 存储pcur的下一个结点Node* Next = pcur->next;// 创建新结点,尾插到pcurNode* newnode = buyNode(pcur->val);pcur->next = newnode;newnode->next = Next;pcur = Next;}
}
struct Node* copyRandomList(struct Node* head) {if(head == NULL){return NULL;}// 1.在原链表上复制结点AddNode(head);// 2.置randomNode* pcur = head;while (pcur) {Node* copy = pcur->next;if (pcur->random != NULL) {copy->random = pcur->random->next;}pcur = copy->next;}// 3.断开链表pcur = head;Node *newHead, *newTail;newHead = newTail = pcur->next;while (pcur->next->next) {pcur = pcur->next->next;newTail->next = pcur->next;newTail = newTail->next;}return newHead;
}

六、写在最后 

 链表既然包括单链表,就还有其他种类啦~

敬请期待“双向链表”~

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Golang | Leetcode Golang题解之第279题完全平方数
  • UE5 C++跑酷练习(Part2)
  • paraFoam 运行 报错 usr/lib/x86_64-linux-gnu/libQt5Core.so 已解决
  • 将git默认的编辑器设置为vin
  • springboot3-web-questions-分析
  • flink 实现大数据实时数据采集
  • 2024年jupyter notebook如何复制单元格cell输出的图片
  • Spring Boot 中常用注解及其用法
  • Spring Security学习笔记(二)Spring Security认证和鉴权
  • [译] 深入浅出Rust基金会
  • Java给定一些元素随机从中选择一个
  • 基于Libero的工程创建
  • export在linux中的作用
  • 音视频入门基础:WAV专题(3)——FFmpeg源码中,判断某文件是否为WAV音频文件的实现
  • 决策树算法介绍:原理与案例实现
  • 【Leetcode】101. 对称二叉树
  • 【Leetcode】104. 二叉树的最大深度
  • ABAP的include关键字,Java的import, C的include和C4C ABSL 的import比较
  • E-HPC支持多队列管理和自动伸缩
  • Fundebug计费标准解释:事件数是如何定义的?
  • JSDuck 与 AngularJS 融合技巧
  • Laravel5.4 Queues队列学习
  • Python_OOP
  • Vue2.x学习三:事件处理生命周期钩子
  • vue脚手架vue-cli
  • 工程优化暨babel升级小记
  • 来,膜拜下android roadmap,强大的执行力
  • 前端路由实现-history
  • 入职第二天:使用koa搭建node server是种怎样的体验
  • 学习笔记TF060:图像语音结合,看图说话
  • 原生js练习题---第五课
  • 找一份好的前端工作,起点很重要
  • 3月7日云栖精选夜读 | RSA 2019安全大会:企业资产管理成行业新风向标,云上安全占绝对优势 ...
  • gunicorn工作原理
  • 阿里云重庆大学大数据训练营落地分享
  • ​【原创】基于SSM的酒店预约管理系统(酒店管理系统毕业设计)
  • ​VRRP 虚拟路由冗余协议(华为)
  • ​人工智能之父图灵诞辰纪念日,一起来看最受读者欢迎的AI技术好书
  • # 20155222 2016-2017-2 《Java程序设计》第5周学习总结
  • #AngularJS#$sce.trustAsResourceUrl
  • #define MODIFY_REG(REG, CLEARMASK, SETMASK)
  • #宝哥教你#查看jquery绑定的事件函数
  • #多叉树深度遍历_结合深度学习的视频编码方法--帧内预测
  • (¥1011)-(一千零一拾一元整)输出
  • (2)关于RabbitMq 的 Topic Exchange 主题交换机
  • (27)4.8 习题课
  • (二)linux使用docker容器运行mysql
  • (附源码)python房屋租赁管理系统 毕业设计 745613
  • (附源码)springboot猪场管理系统 毕业设计 160901
  • (剑指Offer)面试题41:和为s的连续正数序列
  • (接上一篇)前端弄一个变量实现点击次数在前端页面实时更新
  • (三) diretfbrc详解
  • (四)js前端开发中设计模式之工厂方法模式
  • (四)React组件、useState、组件样式
  • .equals()到底是什么意思?