力扣206反转链表:代码实现+图文全解+方法总结(四种方法)

news2024/11/28 18:41:33

文章目录

  • 第一部分:题目描述
  • 第二部分:题解
    • 2.1 方法一:生成新节点到新链表
    • 2.2 方法二:复用旧节点到新链表
      • 🍀 面向过程式思想方法
      • 🍀 面向对象式思想方法
    • 2.3 方法三:递归
    • 2.4 旧链表中移动旧节点

第一部分:题目描述

🏠 链接:206. 反转链表 - 力扣(LeetCode)

⭐ 难度:简单

image-20230511000929405

第二部分:题解

📑 ListNode类

public class ListNode {
    int val;
    ListNode next;

    ListNode() {
    }

    ListNode(int val) {
        this.val = val;
    }

    ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
}

2.1 方法一:生成新节点到新链表

构造一个新链表,从旧链表依次拿到每个节点,创建新节点添加至新链表头部,完成后新链表即是倒序的。

    public static ListNode reverseList(ListNode head) {
        // 1.新链表的头节点,初始为 null
        ListNode newHead = null;
        // 2.指针 tmp 用于遍历 旧链表
        ListNode tmp = head;

        // 3.进行链表遍历
        while (tmp != null) {
            // 3.1 创建一个新节点 node,存储的val值为遍历到的旧链表的节点值 
            ListNode node = new ListNode(tmp.val);
            // 3.2 指定新节点的下一个节点为当前新链表头节点
            node.next = newHead;
            // 3.3 将新节点 node 指定为新的新链表头节点
            newHead = node;
            // 3.4 继续向后遍历旧链表
            tmp = tmp.next;
        }
        // 4.返回新链表头节点
        return newHead;
    }

优化:

    public static ListNode reverseList(ListNode head) {
        // 1.新链表的头节点,初始为 null
        ListNode newHead = null;
        // 2.指针 tmp 用于遍历 旧链表
        ListNode tmp = head;

        // 3.进行链表遍历
        while (tmp != null) {
            // 3.1 创建一个新节点,并指定新节点的值为当前遍历到的旧链表节点值,下一个节点为当前新链表的头节点
            //     修改新链表的头节点为创建的新节点
            newHead = new ListNode(tmp.val, newHead);
            // 3.2 继续向后遍历旧链表
            tmp = tmp.next;
        }
        // 4.返回新链表头节点
        return newHead;
    }

评价:简单直白,就是得新创建节点对象。

2.2 方法二:复用旧节点到新链表

与方法1 类似,构造一个新链表,从旧链表头部移除节点,添加到新链表头部,完成后新链表即是倒序的,区别在于原题目未提供节点外层的容器类,这里提供一个,另外一个区别是并不去构造新节点。

🍀 面向过程式思想方法

    public static ListNode reverseList(ListNode head) {
        // 1.新链表的头节点,初始为 null
        ListNode newHead = null;
        // 2.指针 tmp 用于遍历 旧链表
        ListNode tmp = head;

        // 3.进行链表遍历
        while (tmp != null) {
            // 3.1 临时存储 tmp 的下一个节点
            ListNode node = tmp.next;
            // 3.2 指定 tmp 的下一个节点为当前新链表的头节点,实际是为了拼接已存在的新链表节点
            tmp.next = newHead;
            // 3.3 设置新链表的头节点为 当前tmp指针指向的节点
            newHead = tmp;
            // 3.4 设置 tmp指针 指向临时存储的节点
            tmp = node;
        }
        // 4.返回新链表头节点
        return newHead;
    }

🍀 面向对象式思想方法

我们还可以使用另一种方式来表达该方法的意思:【更加面向对象,如果实际写代码而非刷题,更多会这么做】

    static class List {
        ListNode head;

        public List(ListNode head) {
            this.head = head;
        }

        /**
         * 移除第一个节点
         *
         * @return 被移除的节点
         */
        public ListNode removeFirst() {
            // 1.获取原链表的第一个节点
            ListNode first = head;
            // 2.如果存在节点,则将被删除的节点的下一个节点设置为新的头节点
            if (first != null) {
                head = first.next;
            }
            // 3.返回被删除的节点
            return first;
        }

        /**
         * 添加节点到第一个元素的位置(作为头节点)
         *
         * @param first 需要添加作为新头节点的节点
         */
        public void addFirst(ListNode first) {
            // 1.设置新头节点的下一个节点为原头节点,目的是进行链表节点拼接
            first.next = head;
            // 2.设置新节点为新头节点
            head = first;
        }
    }

    public static ListNode reverseList(ListNode head) {
        // 1.设置 旧链表
        List oldList = new List(head);
        // 2.设置 新链表
        List newList = new List(null);

        // tmp指针用于遍历
        ListNode tmp = null;
        // 3.移除旧链表的当前头节点 node,赋给 tmp。
        // 如果不为空说明移除成功,如果为空说明已经全部遍历完旧链表,旧链表已无节点
        while ((tmp = oldList.removeFirst()) != null) {
            // 4.将从旧链表移除的头节点 node 添加到新链表的头部作为新的头节点
            newList.addFirst(tmp);
        }
        // 5.返回新链表的头节点
        return newList.head;
    }

2.3 方法三:递归

下面为伪码调用过程,假设节点分别是 1 → 2 → 3 → 4 → 5 → n u l l 1 \rightarrow 2 \rightarrow 3 \rightarrow 4 \rightarrow 5 \rightarrow null 12345null,先忽略返回值。

reverseList(ListNode p = 1) {
    reverseList(ListNode p = 2) {
    	reverseList(ListNode p = 3) {
    		reverseList(ListNode p = 4) {
    			reverseList(ListNode p = 5) {
    				if (p == null || p.next == null) {
                        return p; // 返回5
                    }
				}
                // 此时p是4, p.next是5
			}
            // 此时p是3, p.next是4
		}
        // 此时p是2, p.next是3
	}
    // 此时p是1, p.next是2
}

接下来,从 p = 4 开始,要让 5 → 4 5 \rightarrow 4 54 4 → 3 4 \rightarrow 3 43

reverseList(ListNode p = 1) {
    reverseList(ListNode p = 2) {
    	reverseList(ListNode p = 3) {
    		reverseList(ListNode p = 4) {
    			reverseList(ListNode p = 5) {
    				if (p == null || p.next == null) {
                        return p; // 返回5
                    }
				}
                // 此时p是4, p.next是5, 要让5指向4,代码写成 p.next.next=p
                // 还要注意4要指向 null, 否则就死链了
			}
            // 此时p是3, p.next是4
		}
        // 此时p是2, p.next是3
	}
    // 此时p是1, p.next是2
}

最终代码为:

    public static ListNode reverseList(ListNode head) {
        /*
            以 旧链表为 1 -> 2 -> 3 为例
         */

        /*
            递归终止条件是 curr.next == null,目的是到最后一个节点就结束递归
            需要考虑空链表即 p == null 的情况
         */
        if (head == null || head.next == null) {
            return head; // 最后一个节点
        }

        // 比如此时的 head.val 为 2

        // 拿到最后一个节点为3
        ListNode last = reverseList(head.next);
        // 节点3 的下一个节点指向 节点2
        head.next.next = head;
        /*
            节点2 的下一个节点不再指向 节点3
            为什么要加这句话?
            主要是当递归回到 head.val 为1 的时候,如果不加这句话,则会导致 节点1 和 节点2 存在相互指向的问题,
            而导致了在遍历新链表的时候会在 节点1 和 节点2 之间不断循环
         */
        head.next = null;

        // 每次递归方法返回的都是同样的值 —— 最后一个节点
        return last;
    }

Q:为啥不能在的过程中倒序?

A:比如

  • $ 1 \rightarrow 2 \rightarrow 3 $ 如果递的过程中让 2 → 1 2 \rightarrow 1 21 那么此时 2 → 3 2 \rightarrow 3 23 就被覆盖,不知道接下来递给谁
  • 而归的时候让 3 → 2 3 \rightarrow 2 32 不会影响上一层的 1 → 2 1 \rightarrow 2 12

评价:单向链表没有 prev 指针,但利用递归的特性 记住了 链表每次调用时相邻两个节点是谁

2.4 旧链表中移动旧节点

从链表每次拿到第二个节点,将其从链表断开,插入头部,直至它为 null 结束。

image-20230511021447365

image-20230511021521799

image-20230511021549739

    public static ListNode reverseList(ListNode head) {

        if (head == null || head.next == null) {
            return head;
        }

        // 1.设置新链表 newHead 初始指向 旧链表的头节点
        ListNode newHead = head;
        // 2.设置旧链表的头节点,由于 oldHead 不会更改,其实可以直接使用 head,但为了方便理解,还是单独使用一个指针 oldHead0
        ListNode oldHead = head;

        // 用于指向当前需要移动的节点
        ListNode tmp;

        // 3.节点移动

        // 3.1 获取旧链表头节点的下一个节点,直到旧链表只剩下了一个节点即只有旧头节点
        while ((tmp = oldHead.next) != null) {
            // 3.2 将 tmp 从旧链表中移除
            oldHead.next = tmp.next;
            // 3.3 将 tmp 移动到新链表头节点的前面
            tmp.next = newHead;
            // 3.4 将 tmp 设置为新链表新的头节点
            newHead = tmp;
        }

        // 4.返回新链表新的头节点
        return newHead;
    }

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

02:MYSQL---DML

目录 1:介绍 2:DML数据操作 1:介绍 DML英文全称是Data Manipulation Lanquage(数据操作语言),用来对数据库中表的数据记录进行增删改操作。 添加数据 :insert 修改数据:update 删除数据:delete 2:DML数据操作 给指定字段添加数据 insert into 表名(字段名1,…

segment-anything本地部署使用

前言 Segment Anything Model(SAM)是一种先进的图像分割模型,它基于Facebook AI在2020年发布的Foundation Model3,能够根据简单的输入提示(如点或框)准确地分割图像中的任何对象,并且无需额外训…

将项目导入到github全过程

新建仓库 完善仓库信息 然后点击创建仓库 复制仓库地址 将文件上传到git上 我这里要上传IMProject文件夹,所以就在这个文件夹内部,右键鼠标,然后点击git bash here 输入git init ,然后文件夹里面就会多一个.git文件 输入gi…

【IoT】ChatGPT 与 AI 硬件

随着AI的发展,比如最近炒得很火的ChatGPT,还在持续快速迭代更新。 当然了,对于软件和算法,如果你想,每天迭代 10 个版本都可以。 包括科大讯飞的星火认知大模型最近也刚发布。 这就引出了未来一个更大的发展方向&am…

PMP课堂模拟题目及解析(第7期)

61. 为限制项目变更的数量,项目经理制定了严格的变更管理计划,只允许批准减轻重大潜在或实际风险的变更,一位团队成员提出了一个范围变更,该变更将消除对一个落后于进度计划的外部项目的依赖关系。项目经理应该怎么做&#xff1f…

AI绘图实战(九):给热门歌曲做配图 | Stable Diffusion成为设计师生产力工具

S:AI能取代设计师么? I :至少在设计行业,目前AI扮演的主要角色还是超级工具,要顶替?除非甲方对设计效果无所畏惧~~ 预先学习: 安装及其问题解决参考:《Windows安装Stable Diffusion …

迎接新时代挑战:项目管理中的创新与发展

你想知道如何在你的 PM 角色中保持最新状态吗? 您所在的行业是否发展如此之快,以至于有一天您可能不再需要您? 随着人工智能、敏捷和授权团队的兴起,项目经理还需要吗?也许吧,但不是出于您可能期望的原因。…

@vant/weapp

文章目录 一、介绍二、安装1. cd 到项目文件目录2. 使用 npm 安装3. 修改项目配置4. 构建5. 其他文件 三、使用四、【参考】 微信小程序使用vant/weapp组件 一、介绍 Vant 是一个开源的移动端组件库,在微信小程序开发中可以使用该UI库提提供的组件。 使用这个三方…

用户分享 | Dockquery,一个国产数据库客户端的初体验

DockQuery 有话说 DockQuery ,「天狼」也,中原本土狼种。天狼年纪很小,不满一岁,但它有一个伟大的梦想——建造一座能容纳中原群狼的宫殿!它不想再被异域狼欺负,不想被异域狼群挤占生存空间,它…

点到直线距离估计线性回归参数

点到直线距离估计线性回归参数 文章目录 点到直线距离估计线性回归参数[toc]1 推导2 模拟 1 推导 普通最小二乘法(OLS)估计线性回归方程的参数要求残差平方和最小,通过优化方法计算出各参数的估计量。其中残差 e i y i − β 0 − β 1 x i e_iy_i-\beta_0-\beta…

docker安装Nexus3搭建docker私有仓库,并上传镜像

参考:https://blog.csdn.net/gengkui9897/article/details/127353727 nexus3支持的私有库 支持maven(java)、npm(js)、docker、herm、yum、apt、pypi(python)go、等等 1. 下载安装docker(略) 根据系统选择对应版本…

T-SQL游标的使用

一.建表 INSERT INTO cloud VALUES( 你 ) INSERT INTO cloud VALUES( 一会看我 ) INSERT INTO cloud VALUES( 一会看云 ) INSERT INTO cloud VALUES( 我觉得 ) INSERT INTO cloud VALUES( 你看我时很远 ) INSERT INTO cloud VALUES( 你看云时很近 ) 二.建立游标 1.游标的一般格…

微软Office Plus吊打WPS Office?不一定,WPS未来被它“拿捏”了

微软Office Plus吊打WPS Office? 微软的Office是一款非常强大的软件。不仅仅在办公领域中能给我们带来便利,在娱乐和生活的各个方面的管理也能带来很多便利。 当然,作为国产办公软件的排头兵WPS与微软Office的抗衡已经有长达30多年&#xf…

数据库sql语句(经典)

例题: 先来讲讲not in 和not exists的区别,再开始今天的例题(和in,exists相反) not in内外表做笛卡尔积,然后按照条件查询,没有用到索引 not exists是对外表进行循环,每次循环再对内…

从中国制造到中国智造,大眼橙投影仪的进阶之路

刚过去的5月10日是中国品牌日,在这一天各级电视台、广播电台以及平面、网络等媒体,都会安排重要版面来讲中国品牌故事。近日,笔者在与一些品牌的接触中,对大眼橙这个品牌印象颇深,大眼橙是智能投影行业的头部品牌&…

详解:函数栈帧的创建与销毁

函数栈帧的创建与销毁 前期问题函数栈帧定义寄存器的种类与功能汇编指令的功能及含义图解main函数之前的调用调用main函数开辟函数栈帧main函数中创建临时变量并初始化为形式参数创建开辟空间Add函数开辟函数栈帧,创建变量并进行运算释放Add函数栈帧 前期问题解答 铁…

STM32F4的输出比较极性和PWM1,PWM2的关系

PWM 输出比较通道 在这里以通用定时器的通道1作为介绍。 如图,左边就是CNT计数器和CCR1第一路的捕获/比较寄存器,它俩进行比较,当CNT>CCR1, 或者CNTCCR1时,就会给输出模式控制器传送一个信号,然后输出模式控制器就…

基于TextCNN、LSTM与Transformer模型的疫情微博情绪分类

基于TextCNN、LSTM与Transformer模型的疫情微博情绪分类 任务概述 微博情绪分类任务旨在识别微博中蕴含的情绪,输入是一条微博,输出是该微博所蕴含的情绪类别。在本次任务中,我们将微博按照其蕴含的情绪分为以下六个类别之一:积…

Docker部署nacos2.1版本集群

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服…

spring发送qq邮件 + 模板引擎

文章目录 学习链接邮箱配置开启qq邮箱服务相关配置文件 freemarker模板引擎引入依赖配置freemarker编写模板registerTpl.ftl 发送带内嵌图片的邮件 附件效果 学习链接 java邮件发送 Java实现邮件发送 springboot发送QQ邮件(最简单方式) 刘java-Java使用…