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

吃透链表进阶OJ:从 “怕踩坑” 到 “能讲透”

目录

前言:

一、倒数第k个节点

1.1题目思路分析

1.2代码实现

二、相交链表

2.1题目思路分析

2.2代码实现

三、回文链表

3.1题目思路分析       

3.2代码实现

四、拷贝复杂链表

4.1 题目思路分析

4.2代码实现

五、环形链表Ⅰ(重点)

5.1 题目思路分析

5.2代码实现

5.3深入研究

六、环形链表Ⅱ(重点)

6.1题目思路分析

6.2代码实现


前言:

        通过了解单链表的结构与实现,接下来小编将带大家深入探讨单链表的常见操作及其应用场景。我们将通过以下单链表经典算法题来深入理解单链表的特性和应用,每个算法题都配有详细的解题思路、代码实现和复杂度分析,建议读者先尝试独立解决,再参考给出的解决方案。

        

        

一、倒数第k个节点

        

Leetcode链接:倒数第K个节点

题目描述:       

        

1.1题目思路分析

        

思路:

        ①这道题与中间节点类似,回顾一下寻找中间节点的方式:快指针一次走两步,慢指针一次走一步,快指针走到链表的尾部,慢指针恰好走到中间节点的位置。

        

        ②那么我们可以思考假设快指针先走k步,然后两个指针同时走,当快指针走到链表尾部的时候,满指针不就是倒数第k个节点了。        

        

1.2代码实现

        

typedef struct ListNode ListNode;
int kthToLast(struct ListNode* head, int k)
{ListNode *fast=head,*slow=head;while(k--){fast=fast->next;}while(fast){fast=fast->next;slow=slow->next;}return slow->val;  
}

        

二、相交链表

        

 Leetcode链接:相交链表

题目描述:      

2.1题目思路分析

思路:

         ①判断两个链表是否相交: 链表的尾节点作为依据,如果两个链表相交则两个链表的尾节点必然相同,反之两个链表的尾节点不同,两个链表不可能相交。             

        

        ②(查找方式一)寻找两个链表的公共节点:先找到较长的链表,通过暴力查找的方式,遍历长链表的每一个节点时,都在短链表中查找一遍,判断是否两个节点的地址相同。这种查找方式时间复杂度为O(N^2)

         

       ③(查找方式二)寻找两个链表的公共节点:先让较长的链表先查找到与短链表一样的长度的节点位置,然后两者一起查找,判断两个节点的地址是否相同。这种查找方式时间复杂度就为O(N)。       

        

温馨提示:这里判断公共节点,断然不可以用两个节点的值是否相同来作为依据,而应该通过两个节点的地址是否相同来作为依据。  

        

2.2代码实现

        

查找方式一:时间复杂度为O(N^2)

typedef struct ListNode ListNode; 
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{//如果两个链表相交则链表的尾节点必然相同ListNode * pcura=headA;ListNode * pcurb=headB;int lena=1,lenb=1;while(pcura->next){pcura=pcura->next;lena++;}while(pcurb->next){pcurb=pcurb->next;lenb++;}if(pcura!=pcurb) return NULL;//利用假设法判断a,b的链表长度ListNode * longList=headA,* shortList=headB;if(lenb>lena){longList=headB;shortList=headA;}ListNode *p1=longList,*p2=shortList;while(p1){while(p2){if(p1==p2) return p2; p2=p2->next;}p2=shortList;p1=p1->next;}return NULL;
}

        

查找方式二:时间复杂度为O(N)

        

typedef struct ListNode ListNode; 
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{//如果两个链表相交则链表的尾节点必然相同ListNode * pcura=headA;ListNode * pcurb=headB;int lena=1,lenb=1;while(pcura->next){pcura=pcura->next;lena++;}while(pcurb->next){pcurb=pcurb->next;lenb++;}if(pcura!=pcurb) return NULL;//利用假设法判断a,b的链表长度int gap=abs(lenb-lena);ListNode * longList=headA,* shortList=headB;if(lenb>lena){longList=headB;shortList=headA;}while(gap--){longList=longList->next;}while(longList != shortList){longList=longList->next;shortList=shortList->next;}return longList;
}

        

三、回文链表

        

 Leetcode链接:回文链表

题目描述

3.1题目思路分析       

        

思路:

        

        ①对于在一个数组中,判断回文序列我们已经很熟悉了,通过定义双向指针,一个指向头部,另一个指向尾部,通过两个指针不断逼近的方式进行判断数值是否相同,来断定是否为回文序列。

        

        ②对于一个单向不循环链表而言,因为其只能从前往后进行遍历,而不能从后往前进行遍历,所以双向指针的方式失效了,但是也可以通过将单向链表转换为双向链表进行判断,但是这样需要开辟额外的空间,空间复杂度为O(N)。

        

        ③实际上这道题可以通过查找中间节点+反转链表这两个组合拳实现空间复杂度为O(1), 通过查找到中间节点的位置,将中间节点以后的节点进行逆置,将原链表的头节点到中间节点这一部分作为链表1,将逆置后的链表作为链表2,通过分别遍历两个链表判断两个链表的值是否相同。

如下图所示:

        

1.原链表

        

 2. 链表1    

      

3.链表2

        

3.2代码实现

        

//查找链表的中间节点
ListNode * findMid(ListNode * head)
{ListNode *slow=head,*fast=head;while(fast && fast->next){slow=slow->next;fast=fast->next->next;}return slow;
}//反转链表
ListNode * reverseList(ListNode * head)
{ListNode * newhead=NULL;ListNode * pcur=head;while(pcur){ListNode * tmp=pcur->next;pcur->next=newhead;newhead=pcur;pcur=tmp;        }return newhead;
}bool isPalindrome(struct ListNode* head)
{ListNode* pmid=findMid(head);ListNode* newhead=reverseList(pmid);  ListNode* pcur=head;while(pcur!=pmid){if(pcur->val!=newhead->val){return false;}pcur=pcur->next;newhead=newhead->next;}return true;
}

        

四、拷贝复杂链表

        

Leetcode链接:拷贝复杂链表

题目描述:

4.1 题目思路分析

        

思路:

        ①理解深拷贝这个概念,拷贝一个值和指针指向都与当前链表一模一样的链表,但是复制链表的指针都不能指向原链表。

        

        ②我们可以将该链表中每一个节点拆分为两个相连的节点,例如对于链表 A→B→C,我们可以将其拆分为 A→A′→B→B′→C→C′,也就是说在原链表中的每一个节点后插入一个拷贝节点,如下图所示:

        

        

对于任意一个原节点 S,其拷贝节点 S′ 即为其后继节点,我们可以找到每一个拷贝节点S′的任意节点指针的指向,比如说第二个节点任意指针的指向是第一个节点,所以第二个拷贝节点任意指针的指向是第一个节点的后继节点。

        

温馨提示:需要注意原节点的随机指针可能为空,我们需要特别判断这种情况。

        

        ③通过将每个拷贝节点进行剪下来,在串联起来就是我们需要寻找的拷贝链表

        

4.2代码实现

        

typedef struct Node Node;
struct Node* copyRandomList(struct Node* head)
{if(head==NULL) return NULL;Node* pcur=head;//插入节点//在每个节点后面添加一个节点作为复制节点while(pcur){Node *newnode=(Node*)malloc(sizeof(Node));Node * tmp=pcur->next;pcur->next=newnode;newnode->val=pcur->val;newnode->next=tmp;pcur=newnode->next;}//核心环节//修改每个复制节点的random节点pcur=head;while(pcur){Node * copynode=pcur->next;//判断random节点是否为空if(pcur->random==NULL)copynode->random=NULL;elsecopynode->random=pcur->random->next;pcur=copynode->next;}//尾插节点//拆除复制节点pcur=head;Node * dummy=(Node*)malloc(sizeof(Node));Node * ptail=dummy;while(pcur){Node * copynode=pcur->next;ptail->next=copynode;ptail=copynode;pcur->next=copynode->next;pcur=copynode->next; }Node * ret=dummy->next;free(dummy);dummy=NULL;return ret;
}

        

五、环形链表Ⅰ(重点)

        

Leetcode链接:环形链表Ⅰ

题目描述:

        

5.1 题目思路分析

        

思路:

        

①利用快满指针的方式,快指针走二步,满指针走一步,当满指针进入环内,相当于快指针追击满指针,两者一定会在环内相遇。

        

②证明如下:

        我们可以这样理解:假设链表存在环,当 slow 进入环时,设此时 slow 和 fast 在环内的距离为 N。因为 fast 每次走 2 步,slow 每次走 1 步,那么每经过 1 次移动,fast 相对于 slow 就多走了1步,这就使得它们在环内的距离缩小 1。

        

        所以距离从 N 开始,依次变为N-1→N-2→N-3→……→3→2→1→0 不断缩小,最终一定会缩小到 0,此时 slow 和 fast 就相遇了。

        

③如图所示:

        

初始状态:slow和fast同时指向头节点        

        

临界状态:slow刚进入环内

        

最终状态:slow和fast相遇 

        

5.2代码实现

typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head)
{ListNode * fast=head, * slow=head;//如果fast或则fast->next已经是NULL说明此时单链表没有带环 while(fast && fast->next){ //慢指针走一步,快指针走两步//两者一定会在环内相遇  slow=slow->next;  fast=fast->next->next;   if(fast==slow){return true;}   }return false;
}

        

5.3深入研究

思考:如果慢指针走一步,快指针一次走三步、四步、......、N步,是否快慢指针还会在环内相遇呢?

        

接下来:以慢指针走一步,快指针走三步为例进行深入讨论    

        

如下图所示:        

        

初始状态:slow和fast同时指向头节点        

        

临界状态:slow刚进入环内

        

假设 slow刚进入环内时,两者距离相差为N,由于slow指针一次走一步,fast指针一次走三步,所以每走一次两者的距离差距缩小2。

        

若N为偶数时,两个指针之间相差的距离:N→N-2→N-4→N-6→......→4→2→0                   

所以此时slow和fast一定会在环内相遇。

        

若N为奇数时,两个指针之间的相差的距离:N→N-2→N-4→N-6→......→3→1→-1        

        

如何理解距离为-1呢,我们假设整个环的长度大小为C?

        

①我们先看一下距离为1的情况,如图所示:

        

        

②我们再看一下距离为-1的情况,如图所示:

     

总结一下:

          

  • 若初始距离 N 是偶数,第一轮就能追上。

  • 若初始距离 N 是奇数,第一轮追不上;此时再看环的长度 C:
    • 若环长 C 是奇数(此时 N = C - 1 会变成偶数),第二轮能追上。
    • 若环长 C 是偶数(此时 N = C - 1 仍为奇数),永远追不上,会进入 “追不上的死循环”。

        

那么C与N的关系究竟是如何的呢?以slow刚进入环时,这个临界条件进行讨论。

        

如图所示当slow指针刚进入环内:  


      

先通过路程关系列等式:慢指针走了 L,快指针走了 3L 。

        

快指针的路程也可表示为 “慢指针到环入口的距离 L” + “环内已走的 x 圈(x*C)” + “环内剩余距离(C - N)”

                       

则有等式:3 L = L + x * C + C - N

        

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

        

   

再分析奇偶性:左边 2L 是偶数;

        

若假设 “N 为奇数、C 为偶数”,

右边会是 “ 偶数( ( x + 1 ) * C ) - 奇数(N)= 奇数 ”,与左边 “偶数” 矛盾,所以这种情况不可能出现。

        

故而当N为奇数时,C只能为奇数。        

        

综上所述:(fast指针一次走三步,slow指针一次走一步的情况,其余情况可以类比推理)

        

当N为奇数时,C只能为偶数,此时fast指针第一圈追不上,在第二圈追上。

        

当N为偶数时,C不管奇偶性,此时fast指针在第一圈都能够追上。

        

六、环形链表Ⅱ(重点)

        

Leetcode链接:环形链表Ⅱ

题目描述:

        

6.1题目思路分析

        

思路:

        

        ①通过对上一题的思路分析,我们可以知道入环的第一个节点,就是我们的临界情况,即slow指针刚好进入环内。

        

        ②我们可以通过快慢指针的方式,让快指针:fast一次走两步,让慢指针:slow一次走一步,slow指针刚进入环的时候,就是我们寻求的环上第一个节点。

        

        ③ 我们如何知道slow指针刚进入环内呢,接下来我们通过数学进行分析,如何判断slow进入环。         

               

如图所示:slow刚进环的情况        

        

如图所示:slow和fast指针相遇的情况

        

数学推导:(快指针fast一次走两步,慢指针slow一次走一步)

    

情景分析:

            

把链表的 “环” 想象成 “环形跑道”,fast 先跑进 “跑道(环)”,slow 后跑进 “跑道”。当 slow 刚进环时,和 fast 在环内有个距离差 N;由于 fast 速度是 slow 的 2 倍,相当于每走一次,fast 相对于 slow 的距离就缩短 1。

        

因为 fast 一开始就领先在环里,所以在 slow 走完环的第一圈之前,fast 肯定能追上 slow(slow 走不完一整圈就会被追上)。

        

①设置未知数

        

设 “链表表头到环入口的距离” 为 L,“slow 刚进环时与 fast 的环内距离差” 为 N,“环的长度” 为 C。

        

②相遇时:

        

slow 走的路程是 L + N(从表头到环入口的 L,加环内走的 N)。

        

fast 走的路程是 (L + x*C + N)(从表头到环入口的 L,加环内绕了 x 圈的x*C),再加与 slow 的距离 N,且(x>=1),因为 fast 先进环)。

        

③建立等式:

        

L  +  x * C  +  N  =  2 * ( L + N )

        

④化简得:

        

L = x * C - N = (x-1)*C + C-N

        

⑤得出结论:

        

从链表的头节点开始走 与 从快慢指针相遇的位置走,两者距离环入口的距离是一致的,故而可以让一指针从头开始走,另一个指针从快慢指针相遇的位置开始走,两者相遇的位置就是环入口的位置。        

        

6.2代码实现

        

typedef struct ListNode ListNode; 
struct ListNode *detectCycle(struct ListNode *head)
{ListNode * fast=head, * slow=head;ListNode * meet=NULL;//定义一个快指针,一个慢指针//快慢指针相遇,如果链表中无环meet=NULLwhile(fast && fast->next){fast=fast->next->next;slow=slow->next;if(slow==fast){meet=slow;break;}}//定义一个指针从头节点开始走ListNode * start=head;//两个指针相遇的节点就为环入口节点,保证meet不为空指针while(start != meet && meet){meet=meet->next;start=start->next;}return meet;
}

        

既然看到这里了,不妨点赞+收藏,感谢大家,若有问题请指正。

http://www.dtcms.com/a/446931.html

相关文章:

  • 国内做的比较大的外贸电商网站肇庆做网站设计
  • 重庆梁平网站建设哪家好crm系统排行榜
  • 备案信息修改网站负责人政务服务网站建设情况汇报
  • 南昌网站建设制作陕西省住建厅官网
  • 进步主义的异化:个人权利申索如何蜕变成圣母主义和功利主义
  • node-dommatrix
  • 人工智能赋能传统医疗设施设备改造:未来展望与伦理挑战
  • angular网站模板下载公众号必备50个模板
  • 移动端教学视频网站开发怎么制作网站?
  • 德惠市建设局网站商城 小程序
  • 图文网站源码做网站要源代码
  • 怎样下载别人网站自己做的视频html5企业网站模版
  • Git配置与安装并使用Git管理项目
  • 网站dns查询网址大全2345电脑版下载
  • 串扰13-串扰如何影响信号边沿
  • 泰安市住房与城乡建设局网站企业网站建设的目的
  • jetson nano搭建vue3环境
  • 为什么mysql要有主从复制,主库,从库这种东西
  • 进网站后台显示空白wordpress 虾米音乐插件
  • 中国最大的做网站公司常州企业建站系统
  • U支付自动发卡平台使用教程
  • 正规网站优化公司宝思哲手表网站
  • 山西做网站的公司哪个好阜城网站建设价格
  • Raspberry Pi Pico GPIO
  • 网站备案icp过期上海做网站seo
  • 微网站开发 在线商城泰安求职招聘网
  • RSA-NOTES-2
  • 客户管理系统网站模板下载最新的域名
  • Bootstrap5 弹出框
  • 3d网站带后台下载网络公关名词解释