首页 > 其他分享 >链表和双指针框架

链表和双指针框架

时间:2023-06-08 14:06:12浏览次数:29  
标签:快慢 结点 遍历 框架 链表 节点 指针




链表和双指针框架

  • 前后指针:方便链表删除
  • 快慢指针:获取链表倒数第N个元素
  • 快慢指针 + 前后指针:组合问题
  • 快慢指针:相交、判环、起点、长度
  • 双索引指针:合并/分割/拼接链表
  • 链表处理细节
  • 细节1:创建额外的哨兵节点的时机
  • 细节2:链表递归顺序
  • 细节3:虚拟节点
  • 细节4:递归实现双向遍历



 


前后指针:方便链表删除

力扣的链表问题,基本都是单链表。

单链表的操作难点在于,缺了指向前一个节点的指针,而链表删除结点是通过待删除结点的前一个结点。

在力扣里,是让遍历的指针p,从指向【当前节点】改成指向【前一个节点】,这样就有了指向前一个节点的指针。

链表和双指针框架_链表

每次查看节点从 p 变成 p.next,循环终止条件从 p == NULL 变成 p.next==NULL

如果只是遍历问题,这样写OK,但是通常链表问题除了需要寻找到某个特定项元素。

查之外还需要增/删/改,而增/删/改或者实现一定程度的逆向遍历,都需要额外的指针索引。

这样叠加操作比较别扭且容易出错。

我们寻找一种更通用的设计方法,这称为【链表和前后指针框架】。

当删除链表结点时,既需要访问当前结点,也需要访问前一个结点。

就使用两个指针来遍历链表,curr 指针指向当前结点,prev 指针指向前一个结点。

这样两个指针的语义明确,也让你写出的代码更易理解。

链表和双指针框架_数据结构_02

ListNode pre = null;      // 辅助指针,记录 cur 的上一个值
ListNode cur = head;      // 主指针,cur 表示当前结点
while (cur != null) {   
    if (prev == null) {
        // curr 是头结点时的操作
    } else {
        // curr 不是头结点时的操作
    }
    pre = cur;            // 循环中始终维护 pre 是 curr 的前一个节点
    cur = cur.next;       // 更新 cur
}

链表和双指针框架_数据结构_03

链表使用前后指针遍历框架:

  • 判断问题是否为链表遍历-修改,如果是,套用链表遍历框架
  • 思考单步操作,将代码加入遍历框架

例题:

  • [206].反转链表
  • [203].移除链表元素
  1. 两数相加 II
  2. 反转链表 II
     

快慢指针:获取链表倒数第N个元素

链表数据类型的特点决定了只能单向顺序访问,而不能逆向遍历或随机访问。

我们可使用快慢指针的技巧来实现一定程序的逆向遍历。

比如获取中间项:

链表和双指针框架_头结点_04


快指针速度是慢指针速度两倍,快指针移动到末尾时,慢指针指向中间节点。

自定义返回类型 middleNode(ListNode head) {        // 获取链表中间项元素
    ListNode slow = head, fast = head;           // 快慢指针初始化指向 head
    while (fast != null && fast.next != null) {  // 快指针走到末尾时停止
        slow = slow.next;                        // 慢指针走一步
        fast = fast.next.next;                   // 快指针走两步
    }
    return 自定义返回内容;                        // 快指针在终点,慢指针在中点
}

比如获取倒数第N项:

链表和双指针框架_数据结构_05

快指针提前走n步,此时快慢指针之间间隔n-1步,快慢指针再一起走,当快指针走到链表尾部的时候,慢指针指向的就是第n个元素。

ListNode removeNthFromEnd(ListNode head, int k) {
    ListNode fast = head;
    for (int i = 0; i < k; i++)      // 将 fast 前进 k 个元素
        fast = fast.next;            // 这里省略了检测空指针的代码
    // fast 和 slow 指针间隔 k 个同时前进
    // 这里使用了前后指针,将慢指针 slow 变成两个指针 cur 和 pre
    ListNode cur = head;
    ListNode pre = null;
    while (fast != null) {
        prev = curr;
        curr = curr.next;
        fast = fast.next;
    }
    if (prev == null) 
        head = curr.next;
    else 
        prev.next = curr.next;
    return head;
}

例题:

  • [19].删除链表的倒数第 N 个节点
  • [141].判断链表是否有环
  1. 环形链表 II
  2. 排序链表
  3. 链表的中间结点
     

快慢指针 + 前后指针:组合问题

链表组合问题,如先查链表中间节点(特定项),再删除这个节点。

  • 特征1:特定项,对应方法是快慢指针
  • 特征2:删除,对应方法是前后指针

我们融合这俩种方法,使用三个指针即可,分别是快指针、慢指针、慢指针的前一个指针。

这样当快指针到达链表尾部时,慢指针的前一个结点正好是我们要删除的指针。

链表和双指针框架_头结点_06


 


快慢指针:相交、判环、起点、长度

判断环:给出一个链表头结点,判断是否存在环。

  • 使用快慢指针,快指针一次走两步,满指针一次走一步,如果快慢指针能够再次相遇(相交),则说明有环,反之则无环。

环起点:给出一个链表头结点,如果链表有环,则返回入环的第一个节点;如果无环,则返回null

  • 使用快慢指针,先判断是否存在环。如果存在环,则将快指针指向头结点,然后快慢指针每次都各走一步,再次相遇的节点,就是入环的第一个节点。

环长度:给出链表的头结点,假设该链表存在环,求出环长和入环前的长度。

  • 使用快慢指针,当快慢指针相遇时;
  • 计算环长,使快指针暂停,慢指针继续走,并计数,当再次相遇时,慢指针走的周长即环长;
  • 计算入环前长度,使快指针指向头结点,快慢指针每次各走一步,当快慢指针再次相遇时
  • 快指针走的步数即为入环前的长度。

160.相交指针:给两个单链表的头指针headA和headB,找出相交的起始节点,如果不相交则返回null。

用两个指针 p1 和 p2 分别在两条链表上前进,并不能同时走到公共节点,怎么让 p1 和 p2 能够同时到达?

我们可以让 p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A,这样俩个指针走的速度、走的距离都是一样的,肯定会同时抵达终点。

  • 特征:有相交,方法:快慢指针数步子

 


双索引指针:合并/分割/拼接链表

计算机区分和找到目标-编号:人类区别/指挥于其他人/事物的特质是给人起名字的,计算机是通过编号,数据的操作,都是先找到盒子的地址,然后把那个盒子中的数据拿出来处理,处理完的内容,再放回到某个地址中。

21.合并两个有序链表

  • 特征:合并俩个链表成一个,方法:双索引指针
  1. 分隔链表
  • 特征:一个链表分割俩个,方法:双索引指针
  1. 合并 k 个有序链表
  • 特征:给的是数组链表,里面有k个链表,合并k个链表,方法:k索引指针改为用优先队列存储k个索引
     

链表处理细节

细节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:递归实现双向遍历

单链表只能单方向遍历,可以利用递归,对其进行正反两个方向的遍历。

其实递归就是自动维护了一个栈,用迭代的方式就是遍历链表时,节点入栈,链表遍历结束,节点出栈,形成正反两个方向的节点遍历。

例题:

  1. 回文链表


标签:快慢,结点,遍历,框架,链表,节点,指针
From: https://blog.51cto.com/u_13937572/6439442

相关文章

  • PowerDesigner(二)-项目和框架矩阵(转)
    项目和框架矩阵项目是PowerDesigner15的新概念,通过项目系统分析/设计人员可以对模型以及各类文档进行分组。项目也可以包含框架矩阵,以表格的形式体现各个模型之间的关系。项目和框架矩阵解决了如何对模型进行统一管理的问题。1.创建框架矩阵(FEAF-联邦企业架构框架)打开PowerDesig......
  • 基于springmvc+spring+mybatis+hibernate 封装的框架
    基于maven  freemarker2.3.20  spirngmvc3.2.9.RELEASE  spring3.2.9.RELEASE  Hiberante3.6.9.Final(自动建表)  mybatis3.2.7数据交互  druid连接池   cxf3.0.0发布webservice  quartz2.2.1定时任务  jquery   jquery-datata......
  • 进入流程化管理不再是奢望,开源快速开发框架助你梦想成真!
    在数字化进程快速发展的今天,流程化管理是企业做强做大的重要一步。如何实现流程化管理?如何实现数字化发展目标?这些问题都是值得每一个企业深思的重要课题。开源快速开发框架是一种快速帮助企业提质增效的平台软件,可以让每一个企业的流程化管理梦想照进现实。想进入流程化管理吗?一......
  • pytest框架使用
    1.pytest框架1.1.引入常用单元测试框架介绍python:pytest,unittestjava:TestNG,Junitpytest主要作用:找到测试用例执行测试用例判断测试结果生成测试报告pytest默认的测试用例规则(可在pytest.ini中修改规则):模块名必须以test_开头或_test结尾测试类必须以Test开头,并......
  • C++ this 指针
    第一部分this指针的类型可理解为Box*。此时得到两个地址分别为box1和box2对象的地址。实例:#include<iostream>usingnamespacestd;classBox{public:Box(){;}~Box(){;}Box*get_address()//得到this的地址{......
  • C++ 指向类的指针
    C++指向类的指针一个指向C++类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符->,就像访问指向结构的指针一样。与所有的指针一样,您必须在使用指针之前,对指针进行初始化。下面的实例有助于更好地理解指向类的指针的概念:#include<iostream>usin......
  • JAVA 基础面试题(框架)
    一、mybatis    首先,mybatis是一个对象关系映射(orm)框架,是为了解决面向对象与关系数据库的存在互不匹配的现象。也就是说mybatis的关注点在于对象与数据库之间的映射,mybatis会把从数据库中拿到的松散数据进行封装,使开发者直接拿到一个对象。mybatis其实就是对jdbc操作数据库......
  • 【深入浅出Spring原理及实战】「夯实基础系列」360全方位分析和探究SpringMVC的核心原
    SpringMVC简介SpringWebMVC是一种基于Java的轻量级Web框架,它实现了WebMVC设计模式,使用VC架构模式的思想将web层进行职责解耦。这种请求驱动类型的框架使用请求-响应模型,旨在简化Web开发过程。使用SpringWebMVC,我们可以更加高效地开发Web应用程序,而不必为了每个接口编写一个Ser......
  • pytest + yaml 框架 -32.re 正则解析返回结果
    前言pytest-yaml-yoyo插件可以支持3种表达式提取接口返回结果,jsonpath和jmespath适合解析返回的json数据。非json数据的结果可以用re正则表达式取值。re正则取值访问我的博客地址https://www.cnblogs.com/yoyoketang/test_re.yml用例文件内容#上海悠悠wx:2833404......
  • 手写spring框架
    Spring IoC容器的实现原理:工厂模式 + 解析XML + 反射机制。我们给自己的框架起名为:myspring(我的春天)1. 第一步:创建模块myspring 62采用Maven方式新建Module:myspring打包方式采用jar,并且引入dom4j和jaxen的依赖,因为要使用它解析XML文件,还有junit依赖。<?xmlversion="1.0"en......