代码随想录—链表

115 阅读7分钟

链表

基础概念:

链表是一种动态数据结构,由一系列节点组成,每个节点包含数据和指向下一个节点的指针。

定义: 构造函数分别对应默认值,给定val值以及给定val值和后续节点三种情况

image.png

1. 移除链表元素 虚拟头节点 手动释放空间

例题:203. 移除链表元素

思路1: 分类讨论删除头节点和中间节点,需注意的是创建一个临时节点tmp 来存储节点信息辅助删除,删除后要手动释放存储空间并把tmp置为空(否则tmp为悬空指针) image.png

思路2: 设置一个虚拟头节点指向原链表的头部,此时对链表所有节点的操作都一致,全部节点的删除操作都是思路1的中间节点删除操作 image.png

思路3: 递归,总体由终止条件,递归过程,回溯过程组成,需考虑清楚每一轮递归的相同操作,并定好递归终止条件,思考时应当从终止条件往回检查 image.png

2. 链表的增删查操作

思路: 类的构造函数先默认构造一个虚拟头节点以及链表大小size,这样后续操作全部统一,size用于查询目标节点

  1. 增加:更新节点指向关系,size++

    • 头部插入:直接在虚拟头节点后插入即可,公式化更新指向关系
    • 尾部插入:先遍历到链表末尾,遍历指针cur指向尾部节点,cur->next = newNode
    • 中间插入:遍历查询,cur指向虚拟头节点,while(index--)cur->next即为目标节点
  2. 查询:同中间插入,cur指向虚拟头节点,要让cur指向目标节点的前一位,要找第几个节点就要循环多少次,简单的检查方法就是带一个极端值,例如index=0,看循环是否满足

  3. 删除:找目标节点原理同查询,删除操作即让cur->next = cur->next->next,需要额外初始化一个tmp节点指向目标节点用于之后删除空间

例题:707. 设计链表 image.png

3. 反转链表 双指针 前->后递归 后->前递归

例题:206. 反转链表

思路1: 双指针(迭代),准确的说其实是三指针,两个指针用于更新前两个节点的指向关系,一个临时指针用于存储后一个节点信息,更新完指向关系(反转)后两个指针后移一位继续上述操作 image.png

思路2: 从后往前递归,相当于回溯过程中一直是屁股的节点反转好了再接上前面的。链表head为空或指向尾节点时终止递归,并向上一层递归返回head作为新链表的头节点,递归过程:先初始化一个newhead作为“不变量”存储反转后链表的头,此后返回的一直是链表的头节点,但在每个节点的递归操作中会修改尾部节点的指向关系

在每一轮递归操作中,对每个节点都使head的后一个节点指向head,head作为反转后链表的尾部节点指向nullptr

例:1->2->3->4 ... ...

1->2 在第一轮变为 2->1->nullptr,2->3在第二轮变为3->2->nullptr,后续同理,但在回溯操作中是从后往前,所以第二轮的指向nullptr反而先执行,不会影响前面的指向变动,即先有 3->2->nullptr,再有 2->1->nullptr image.png

思路3: 从前往后递归,设计要传入pre和cur两个指针,额外写一个递归函数用于反转操作,在递归函数中的终止条件为每轮的pre指向尾节点,cur->nullptr,每轮递归传入下一轮的pre和cur分别是本轮的cur和tmp,最开始进入递归函数的pre = NULLcur = head,中间的反转操作同双指针操作一致 image.png

4. 两两交换相邻节点 临时存储后续节点

思路: 交换操作,需注意交换操作中节点后续节点的变化,先把没变的重新指向或用临时节点存储,避免找不到后续节点

例题:24. 两两交换链表中的节点 image.png

5. 删除倒数第N个节点 长度为n+1的滑动窗口

思路: 要找倒数第n个节点,先让快指针后移n次,此时形成一个长度为n的窗口,再让快慢指针(窗口)一起后移直到fast->next=NULL,但此时慢指针刚好指向倒数第n个节点,慢指针要指向目标节点的前一个节点才能删除目标节点,所以要让快指针后移n+1次

例题:19. 删除链表的倒数第 N 个结点 image.png

6. 链表相交

例题:面试题 02.07. 链表相交

思路1: 拼接链表,AB同时遍历在链表A后面接上链表B,B后面接上A,此时两条新的链表长度相等且末尾即是要找到相交链表,需注意这里求两个节点是否相等的操作是看两个节点的地址空间是否一致,即比较的这两个节点是不是同一节点,而链表中一个节点后续只能指向一个节点,所以只要有一个节点的地址空间相同,后续的节点一定相同,即后续一定是相交链表

链表A + 链表B = 链表C1
链表B + 链表A = 链表C2
A -> a1 a2 c1 c2 c3
B -> b1 b2 b3 c1 c2 c3
C1 -> a1 a2 c1 c2 c3 b1 b2 b3 c1 c2 c3
C2 -> b1 b2 b3 c1 c2 c3 a1 a2 c1 c2 c3
此时C1和C2的长度相同,而C1和C2结尾相同的节点就一定是相交链表的头节点

image.png

思路2: 对齐尾部,移动指针对齐向后遍历 把两条链表尾部对齐,即让两个指针对齐然后一起往后遍历找相等节点,其实与思路1相似,都是让相交链表所在的尾部对齐(让两条链表的指针在“同一位置”往后遍历) image.png

7. 环形链表

思路: 先进行数学推导找关系假设从头结点到环形入口节点的路程为 x,环形入口节点到两指针相遇节点的路程为 y,从相遇节点再到环形入口节点的路程为 z

相遇时: slow 指针走过的路程: x + y,fast 指针走过的路程:x + y + n (y + z),n 为 fast 指针在环内走了 n 圈才遇到 slow 指针,(y+z)为环形一圈的路程

因为 fast 指针是一步走两个节点,slow 指针一步走一个节点, 所以 fast 指针走过的节点数 = slow 指针走过的节点数 * 2:(x + y) * 2 = x + y + n (y + z),化简整理得:x = (n - 1) (y + z) + z,注意这里n一定是大于等于1的,因为 fast 指针至少要多走一圈才能相遇 slow 指针

以 n = 1 为例,即fast指针在环形里转了一圈之后,就遇到了 slow 指针,此时公式化为 x = z,这说明,从头结点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点,那么这两个指针相遇的节点就是环形的入口节点

例题:142. 环形链表 II image.png