链表和双指针框架
Posted Debroon
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了链表和双指针框架相关的知识,希望对你有一定的参考价值。
链表和双指针框架
链表遍历和左右指针框架
力扣的链表问题,基本都是单链表。
单链表的操作难点在于,缺了指向前一个节点的指针,而链表删除结点是通过待删除结点的前一个结点。
在力扣里,是让遍历的指针p
,从指向【当前节点】改成指向【前一个节点】,这样就有了指向前一个节点的指针。
每次查看节点从 p
变成 p.next
,循环终止条件从 p == NULL
变成 p.next==NULL
。
这样操作比较别扭且容易出错,而且力扣主要是考算法设计,C++语言特性的二级指针、创建额外的哨兵节点都不能用。
我们寻找一种更通用的设计方法,这称为【链表和左右指针遍历框架】。
- 双指针技巧主要分为两类:左右指针和快慢指针。
- 左右指针,就是两个指针相向而行或者相背而行。
- 快慢指针,就是两个指针同向而行,一快一慢,滑动窗口也是快慢指针。
当删除链表结点时,既需要访问当前结点,也需要访问前一个结点。
就使用两个指针来遍历链表,curr
指针指向当前结点,prev
指针指向前一个结点。
这样两个指针的语义明确,也让你写出的代码更易理解。
ListNode pre = null; // 辅助指针,记录 cur 的上一个值
ListNode cur = head; // 主指针,cur 表示当前结点
while (cur != null)
if (pre == null) // cur 是头结点时的操作
else // cur 不是头结点时的操作
pre = cur; // 循环中始终维护 pre 是 curr 的前一个节点
cur = cur.next; // 更新 cur
使用链表和双指针遍历框架:
- 判断问题是否为链表遍历-修改,如果是,套用链表遍历框架
- 思考单步操作,将代码加入遍历框架
例题:
- [206].反转链表
- [203].移除链表元素
-
- 两数相加 II
-
- 反转链表 II
链表逆向遍历和快慢指针
链表数据类型的特点决定了它只能单向顺序访问,而不能逆向遍历或随机访问。
我们需要使用快慢指针的技巧来实现一定程序的逆向遍历,能减少重复遍历次数,这也是为什么快慢指针常用于链表问题。
自定义返回类型 middleNode(ListNode head)
ListNode slow = head, fast = head; // 快慢指针初始化指向 head
while (fast != null && fast.next != null) // 快指针走到末尾时停止
slow = slow.next; // 慢指针走一步
fast = fast.next.next; // 快指针走两步
return 自定义返回内容; // 快指针在终点,慢指针在中点
例题:
- [19].删除链表的倒数第 N 个节点
- [141].判断链表是否有环
-
- 环形链表 II
-
- 排序链表
-
- 链表的中间结点
链表处理细节
细节1:创建额外的哨兵节点的时机
当需要创造一条新链表的时候,可使用虚拟头结点简化边界情况的处理。
例题:
- 21.合并两个有序链表,把两条有序链表合并成一条新的有序链表。
- 86.分隔链表,把一条链表分解成两条链表。
细节2:链表递归顺序
void traverse(ListNode root) // 递归遍历单链表
if (root == null) return; // 最基础的情况
print(root.val); //【前序位置】
traverse(root.next);
print(root.val); //【后序位置】
前序位置和后序位置的区别是什么?
- 前序位置的
print(root.val)
:遍历链表,沿途输出节点,顺序遍历 - 后序位置的
print(root.val)
:遍历链表,一直遍历,直到遇到最基础的情况才能计算出子问题的解,逆序遍历。
细节3:虚拟节点
细节4:穿针引线
细节5:先穿后排,再判空
以上是关于链表和双指针框架的主要内容,如果未能解决你的问题,请参考以下文章