单链表经典OJ题(二)

目录

1、链表的回文结构

2、链表分割

3、随机链表的复制

4、相交链表

5、环形链表

6、环形链表二


1、链表的回文结构

链表的回文结构_牛客题霸_牛客网 (nowcoder.com)

回文结构类似于:1->2->3->3->2->1,具体解题思路如下:

1、利用快慢指针的特性找到链表的中间结点

快慢结点的特性:如果链表有效结点个数为单数,则当快指针指向的结点为空或快指针指向的下一个结点为空时,慢指针指向该链表的中间结点。如果链表有效结点个数为双数,则当快指针指向的结点为空或快指针指向的下一个结点为空时,慢指针指向该链表的中间两个结点的后一个结点,也就是上图所示的情况

2、中间结点后的结点逆置

这里的可以忽视快指针quick,p1指针的作用就是为了记住p指针的下一个结点,这里的主角就是慢指针slow和p指针,先令p->next指向此时slow指向的中间结点,此时完成其中一对结点的方向逆置,接下来两个结点都向后走,先让slow指向p指针所指向的结点,然后再让p指针指向p1所指向的结点,最后p1再往后走一个结点,至此一次逆置循环结束,第二次循环与第一次循环的执行方式一样,最后结果如图二所示,第二次循环结束后p仍未指向空所以还可以进行第三次循环,当第三次循环时由于p1在第二次循环结束时已经指向了NULL,所以第三次循环令p指向p1所指向的位置时,p应该指向空,此时slow已经指向链表的最后一个结点了,最后由于p在第三次循环后变为空指针所以不会再有第四次循环

3、开始进行多种比较,主要还是比较一对儿结点中存放的有效值是否相同

经过上述的逆置操作,此时slow指针和A指针应该已经分别指向链表的两端结点,此时首先应该保证A指针不会与slow指针重合如果重合直接返回true即可,这一步其实与刚开始时判断A->next是否为空的操作有异曲同工之妙,如果不重合则判断此时两指针所指向结点中存放的有效值是否相等,如果不相等则直接判断为非回文结构返回flase,如果相等则令两指针都向前移动一个结点,后续进行逐对儿判断链表两端结点中存放的有效值是否相同,如果直到A指针指向的结点与slow指针指向的结点相邻时仍未返回flase2,即如上图二所示的情况,此时我们应该就可以知道该链表为回文结构了,故当A->next == slow时可以判断链表为回文结构

最终代码如下:

class PalindromeList {
public:bool chkPalindrome(ListNode* A) {//如果链表不存在,则返回值为falseif(A==NULL)return false;//如果链表只有一个有效结点,则返回值为trueelse if(A->next==NULL)return true;//利用快慢指针找出中间节点ListNode* quick = A;ListNode* slow  = A;只要快指针及快指针的下一个结点不为空则快慢指针继续向前移动while(quick != NULL && quick->next != NULL){//快指针每次移动两个结点quick=quick->next->next;//慢指针每次移动一个结点slow=slow->next;}//中间结点后结点的逆置ListNode* p=slow->next;ListNode* p1=p->next;       while(p!=NULL){p->next=slow;slow=p;p=p1;p1=p1->next;}//开始进行多种比较,主要还是比较一对儿结点中存放的有效值是否相同while(A!=slow){if((A->val)!=(slow->val)){return false;}else{if(A->next==slow){return true;}A=A->next;slow=slow->next;}}return true;}
};

2、链表分割

链表分割_牛客题霸_牛客网 (nowcoder.com)

假设链表为:5->7->9->4->6->2->3,其中x=4,具体解题思路如下:

1、创建一个用于存放结点中val小于x的链表

链表有哨兵位时,对链表进行尾插就无需判断链表是否为空

先创建一个存放无效值的哨兵位结点,开始时lesshead和lesstail都指向该哨兵位的结点空间,但是lesstail后续在进行尾插时会移动,而lesshead在后续拼接链表时才会起作用

ListNode* lesshead,*lesstail;
lesshead=lesstail=(ListNode*)malloc(sizeof(ListNode));
lesshead->val = -1;
lesstail->next=NULL;

2、创建一个用于存放结点中val大于等于x的链表(图中少写了个等于)

链表有哨兵位时,对链表进行尾插就无需判断链表是否为空

先创建一个存放无效值的哨兵位结点,开始时greathead和greattail都指向该哨兵位的结点空间,但是greattail后续在进行尾插时会移动,而greathead在后续拼接链表时才会起作用

ListNode* greathead,*greattail;
greathead=greattail=(ListNode*)malloc(sizeof(ListNode));
greathead->val = -1;
greattail->next=NULL;

3、遍历原链表,将原链表中的符合条件的结点分别尾插进新创建的两个链表中

while(pHead)
{//如果原链表中结点的val值小于x,就将该结点尾插进合适的链表中,同时pHead和lesstail均指向下一个结点if(pHead->val<x){lesstail->next=pHead;pHead=pHead->next;lesstail=lesstail->next;}//如果原链表中结点的val值大于等于x,就将该结点尾插进合适的链表中,同时pHead和greattail均指向下一个结点else{greattail->next=pHead;pHead=pHead->next;greattail=greattail->next;}
}

最终结果如下: 

4、拼接两个新创建的链表,并进行一些善后操作

//由于题目要求是将所有小于x的结点排在其余结点之前,所以要令lesstail->next = greathead->next
lesstail->next=greathead->next;//为拼接后的链表创建一个新的头结点newHead
ListNode* newhead=lesshead->next;//拼接结束后由于lessHead和greatHead这两个哨兵位已经没有用处了,所以要将为这两个哨兵位申请的内存空间释放掉
free(lesshead);
free(greathead);//最后一定要将greattail->next置为空,否则还是会报错
greattail->next=NULL;//应题目要求,返回重新排列后的链表的头指针newHead
return newhead;

最终代码如下:

/*
struct ListNode {int val;struct ListNode *next;ListNode(int x) : val(x), next(NULL) {}
};*/
class Partition {
public:ListNode* partition(ListNode* pHead, int x) {//创建一个用于存放结点中val小于x的链表ListNode* lesshead,*lesstail;lesshead=lesstail=(ListNode*)malloc(sizeof(ListNode));lesshead->val = -1;lesstail->next=NULL;//创建一个用于存放结点中val大于x的链表ListNode* greathead,*greattail;greathead=greattail=(ListNode*)malloc(sizeof(ListNode));greathead->val = -1;greattail->next=NULL;//遍历原链表,将原链表中的符合条件的结点分别尾插进新创建的两个链表中while(pHead){//如果原链表中结点的val值小于x,就将该结点尾插进合适的链表中,同时pHead和lesstail均指向下一个结点if(pHead->val<x){lesstail->next=pHead;pHead=pHead->next;lesstail=lesstail->next;}//如果原链表中结点的val值大于x,就将该结点尾插进合适的链表中,同时pHead和greattail均指向下一个结点else{greattail->next=pHead;pHead=pHead->next;greattail=greattail->next;}}//由于题目要求是将所有小于x的结点排在其余结点之前,所以要令lesstail->next = greathead->nextlesstail->next=greathead->next;//为拼接后的链表创建一个新的头结点newHeadListNode* newhead=lesshead->next;//拼接结束后由于lessHead和greatHead这两个哨兵位已经没有用处了,所以要将为这两个哨兵位申请的内存空间释放掉free(lesshead);free(greathead);//最后一定要将greattail->next置为空,否则还是会报错greattail->next=NULL;//应题目要求,返回重新排列后的链表的头指针newHeadreturn newhead;}
};

3、随机链表的复制

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

具体解决思路如下:

1、利用循环插入新节点A' B' C',将原来的A->B->C的链表结点变为A->A'->B->B'->C->C':

for (struct Node* node = head; node != NULL; node = node->next->next) {struct Node* nodeNew = (struct Node*)malloc(sizeof(struct Node));nodeNew->val = node->val;nodeNew->next = node->next;node->next = nodeNew;}

2、将上一次循环结束后位于末尾的node指针和nodeNew指针重新回到原来的位置,接下来进行A B C三个结点的random指针的拷贝,具体方式是利用三元运算符判断node->random是否为空,如果为空则新开辟的结点A'的random就指向空,如果node->random不为空则令新开辟的结点A'的random指向node指针所指向的结点中的random指针指向的结点的下一个结点即node->random->next,这是因为我们在第一次循环时已经将链表的整体顺序变为了A->A'->B->B'->C->C'的形式,为了保证深拷贝的完整性,新创建的三个结点A' B' C'中的random指针只能指向包含NULL在内的这三个新建立结点,正因如此他不能仅仅令nodeNew->random=node->random这样只是让新节点的random指针指向原来的某个结点而非新创建的三个结点,而只有令nodeNew->random = node->random->next才能做到这点

for (struct Node* node = head; node != NULL; node = node->next->next) {struct Node* nodeNew = node->next;nodeNew->random = (node->random != NULL) ? node->random->next : NULL;}

3、 为深拷贝的链表创建一个新的头结点headNew,再次利用循环切断新旧链表之间的指针联系,

最后返回创建的新的头结点headNew

其实这一次的循环就是为了断开图中新旧链表相连的蓝线并将新链表结点串起来

struct Node* headNew = head->next;
for (struct Node* node = head; node != NULL; node = node->next) {struct Node* nodeNew = node->next;node->next = node->next->next;nodeNew->next = (nodeNew->next != NULL) ? nodeNew->next->next : NULL;}
return headNew;

最终代码如下:

struct Node* copyRandomList(struct Node* head) {if (head == NULL) {return NULL;}for (struct Node* node = head; node != NULL; node = node->next->next) {struct Node* nodeNew = (struct Node*)malloc(sizeof(struct Node));nodeNew->val = node->val;nodeNew->next = node->next;node->next = nodeNew;}for (struct Node* node = head; node != NULL; node = node->next->next) {struct Node* nodeNew = node->next;nodeNew->random = (node->random != NULL) ? node->random->next : NULL;}stru ct Node* headNew = head->next;for (struct Node* node = head; node != NULL; node = node->next) {struct Node* nodeNew = node->next;node->next = node->next->next;nodeNew->next = (nodeNew->next != NULL) ? nodeNew->next->next : NULL;}return headNew;
}

4、相交链表

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

        我们令pA和pB走过一样的路程即:pA走过的路程是:A的路程+NULL+B的路程;pB走过的路程是:B的路程+NULL+A的路程;

        如果两个链表有相交结点那么,pA和pB在走这一段路的时候一定会在某一个位置相逢即pA=pB,此时返回pA即为相交结点,如果两个链表不相交则仍返回pA此时pA==NULL(这种情况可以自己画图演示一下)

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {//首先你要保证这两个链表都不为空if (headA == NULL || headB == NULL) {return NULL;}struct ListNode *pA = headA, *pB = headB;while (pA != pB){pA = pA == NULL ? headB : pA->next;pB = pB == NULL ? headA : pB->next;}return pA;
}

5、环形链表

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

         仍然是利用快慢指针的特性来解决这个问题,快指针的速度比慢指针的速度快,如果链表存在一个环那么快指针总会与慢指针相遇

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

6、环形链表二

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

 本题要求不仅要判断链表是否带环,还要返回该环的第一个结点的值,具体解题思路如下:

1、设置快慢指针,判断链表是否为空以及是否带环

struct ListNode *slow = head, *fast = head;
while (fast != NULL)
{}
return NULL;

在第一次循环时如果快指针会指向空那么证明该链表不存在,直接返回NULL,接下来fast != NULL的作用就是为了判断链表是否带环,如果在fast指针移动的过程中fast会指向NULL那么证明该链表不带环仍需返回NULL

2、如果该链表存在那么就去判断该链表是否只有一个有效结点,如果是则也返回NULL,如果不是则快慢指针向前移动

 if (fast->next == NULL) {return NULL;}slow = slow->next;fast = fast->next->next;

3、如果该链表带环,那么在快慢指针移动的过程中二者一定会相遇,但是每次相遇的位置不一定

当快慢指针相遇时,令一个临时指针ptr指向链表的头结点,并令该指针也开始向前逐步移动,直到它与慢指针重合,此时返回ptr指向的结点,该结点就是进入环的第一个结点,其中蕴含的数学思想如下:

假设链表有a+b个结点,其中a个结点表示未进入环前的链表结点个数,b个结点表示环中结点的个数,假设两指针相遇时慢指针走了s步,那么快指针就走了2s步(快指针每次走两步慢指针每次走一步),而快指针必然比慢指针先进入环中,而后续快指针有会在环中赶上慢指针,所以在两者相遇时快指针一定已经走了n圈环了即n*b步,所以在相遇时f=s+nb(可以理解为:你走过的路我都走过,我为了追上你一直在原地徘徊只为了再次遇上你),而由于f=2s,所以s = nb,即慢指针走了n遍环。我们还可以知道的是从链表的第一个结点开始走a+nb步必然会到达环的第一个结点,而此时我们已经知道了慢指针走了nb步,所以慢指针只需要再走a步就可以到达环的第一个结点了,而怎么样才能保证走a步呢?答案是链表第一个结点处,所以我们令ptr指向链表的第一个结点然后令ptr和慢指针都一直向前走,直到二者相遇时证明ptr也开始进入环了,而此时ptr肯定已经走了a步,慢指针也肯定走了a步,所以此时二者相遇的位置就是入环的第一个结点。

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

~over~

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

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

相关文章

快手自动引流软件的运行分享,以及涉及到技术与核心代码分享

先来看实操成果&#xff0c;↑↑需要的同学可看我名字↖↖↖↖↖&#xff0c;或评论888无偿分享 一、引言 引流是任何网络创业者或营销人员必备的技能之一。手动引流不仅耗时&#xff0c;而且效果难以保证。因此&#xff0c;自动引流软件应运而生&#xff0c;成为许多人的得力助…

MATLAB中ginput函数用法

目录 语法 说明 示例 标识点和绘制坐标 返回用于选择坐标的按键 标识地理坐标区上的点 ginput函数的功能是标识坐标区坐标。 语法 [x,y] ginput(n) [x,y] ginput [x,y,button] ginput(___) 说明 [x,y] ginput(n) 可用于标识笛卡尔坐标区、极坐标区或地理坐标区内…

java 实现 CAN口通讯

1、引入第三方库 链接&#xff1a;https://pan.baidu.com/s/1JC-Bi_Qgts5a-tGo28JcTQ?pwd6533 提取码&#xff1a;6533 将第三方库 放在libs包里&#xff0c;并在pom文件中增加依赖 <dependency><groupId>libsocket-can-java</groupId><artifactId>…

【移远QuecPython】EC800M物联网开发板的音乐播放(PWM蜂鸣器播放生日快乐歌,Sound模块播放音频)

【移远QuecPython】EC800M物联网开发板的音乐播放&#xff08;PWM蜂鸣器播放生日快乐歌&#xff0c;Sound模块播放音频&#xff09; 效果&#xff1a; 【移远QuecPython】EC800M开发板外置功放重金属和PWM音调&#xff08;BUG调试记录&#xff09; 文章目录 PWM蜂鸣器播放播放…

小程序中如何设置门店信息

小程序是商家转型升级的利器&#xff0c;小程序中门店信息的准确性和完整性对于用户的体验和信任度都有很大的影响。下面具体介绍门店信息怎么在小程序中进行设置。 在小程序管理员后台->门店设置处&#xff0c;可以门店设置相关。主要分为2个模块&#xff0c;一个是门店级…

轻量封装WebGPU渲染系统示例<29>- 深度模糊DepthBlur(源码)

实现方式: step1. 通过mrt机制&#xff0c;输出颜色和深度相关数据的两张rtt纹理。 step2. 基于上述颜色纹理&#xff0c;生成一张模糊之后的新rtt纹理。 setp3. 基于深度(也就是距离摄像机的远近)数据&#xff0c;合成颜色和模糊纹理数据&#xff0c;并最终输出。 当前示例…

虚幻C++基础 day4

虚幻中的UI 虚幻中的比较常用的UI&#xff1a;Widget Blueprint又称UMG虚幻中的两种布局&#xff1a; 网格布局锚布局 创建Widget Blueprint 网格布局 有点类似Qt中的网格布局&#xff0c;将UI面板进行行列切分Horizontal Box&#xff1a;水平分布Vertical Box&#xff1a;…

pytorch-gpu(Anaconda3+cuda+cudnn)

文章目录 下载Anaconda3安装&#xff0c;看着点next就行比较懒所以自动添加path测试 cuda安装的时候不能改路径如果出现报错&#xff0c;关闭杀毒软件一直下一步就好取消勾选“CUDA”中的“Visual Studio Intergration”一直下一步即可测试安装成功 cudnn解压后将这三个文件夹复…

tcpdump wireshark简单使用

tcpdump工作原理 tcpdump 是 Linux 系统中非常有用的网络工具&#xff0c;运行在用户态&#xff0c;本质上是通过调用 libpcap 库的各种 api 来实现数据包的抓取功能&#xff0c;利用内核中的 AF_PACKET 套接字&#xff0c;抓取网络接口中传输的网络包。查 看 tcpdump 的 手册…

振南技术干货集:深入浅出的Bootloader(2)

注解目录 1、烧录方式的更新迭代 1.1 古老的烧录方式 (怀旧一下&#xff0c;单片机高压烧录器。) 1.2 ISP 与ICP 烧录方式 (还记得当年我们玩过的 AT89S51?) 1.3 更方便的 ISP 烧录方式 1.3.1串口 ISP &#xff08;是 STC 单片机成就了我们&#xff0c;还是我们成就了…

VS设置--查看引用库源代码

1.工具-->选项-->文本编译器-->C#-->高级-->勾选支持导航到反编译源(试验)

45 深度学习(九):transformer

文章目录 transformer原理代码的基础准备位置编码Encoder blockmulti-head attentionFeed Forward自定义encoder block Deconder blockEncoderDecodertransformer自定义loss 和 学习率mask生成函数训练翻译 transformer 这边讲一下这几年如日中天的新的seq2seq模式的transform…