难题:反转链表

定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。

思考题:

  • 请同时实现迭代版本和递归版本。
数据范围

链表长度 [0,30]。

样例
输入:1->2->3->4->5->NULL输出:5->4->3->2->1->NULL

正好这个题目用于加深理解递归和迭代的含义与区别。

 一、递归的写法

//递归的写法
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:ListNode* reverseList(ListNode* head) {// 递归基:如果head为nullptr或者head->next为nullptr,直接返回headif(head==nullptr||head->next==nullptr){return head;}
/*这里就是正常的逻辑,刚开始判断这个头指针是不是为空,为空就不行;还有是不是只有一个结点,如果只有一个结点,那也不需要做反转链表的操作,所以才会这么写,这就相当于这个递归的终止条件。*/// 递归调用,返回反转后的链表头结点newHeadListNode* newhead=reverseList(head->next);
/*这里就是让head指针不断前进,当指针为空时,就回溯来解决问题,这里就是这个意思。*/// 将当前head的下一个节点的next指针指向head,然后将head的next指针设为nullptr,完成当前节点的反转head->next->next=head;head->next=nullptr;
/*这里的这个递归条件看着很简单,其实很难理解,这是回溯的结果,而且需要画图来结合分析,不然你是搞不清楚指针的变化的,图我会下面画的。*/return newhead;// 返回新的头结点
/*输出的结果虽然也是值,本来这个newhead只是个指针,按道理来说返回的应该时地址值,可是这运行结果直接返回值序列,为什么呢?这是因为这个格式下的代码只要把运行的主体逻辑写好就是了,那些细枝末节的操作别人都做好了,就不用管了,这是我的理解*/}
};

对这个代码的理解:观察题目可看到,已经为我们建立了头结点head,在链表里面,那个链表一般叫什么名字,那他的头指针就叫什么名字。这个head不仅是这个反转前链表的头结点,也是一个指向这个头结点的一个指针,这个head存储着这个头结点的地址,此时的head这个指针是指向这个头结点的,这是题目告诉我们的,我们就不需要额外的去管反转前头结点的事情了。

1、递归与迭代的区别:

   我的理解就是,感觉迭代似乎更倾向于具体的操作的样子,而递归是更倾向于逻辑来写代码。

迭代通常更接近具体的操作。它涉及到明确的循环结构或者迭代过程,直接处理每一步操作,更像是一种线性的、直截了当的方式来解决问题。因为迭代往往需要处理每一步的细节,所以它更强调具体的操作步骤和实现细节。

递归则更倾向于逻辑的抽象和分解。递归算法通过将问题分解为更小的问题来解决,这需要设计出适当的递归调用和基本情况,以确保递归能够正确地终止。递归更侧重于逻辑的设计和分解问题的能力,因为它要求将问题抽象为一系列更简单的步骤或者子问题,然后通过逐层回溯来达到解决原始问题的目的。

因此,迭代在实现上可能更加直观和操作性强,而递归则更注重逻辑的清晰性和对问题结构的抽象能力。选择使用迭代还是递归通常取决于问题的性质、算法的效率要求以及程序员个人的偏好和经验。而且,因为递归和迭代都是反复执行操作,而两者又有点差别,递归通常不怎么用循环,因为他要自己调用自己;而迭代更多的是重复某个操作,循环那些是经常用的。这是这两个的小区别,但大体上逻辑都是不断重复相同的操作,可根据自己的需要来决定使用哪种方法。

好,上面是我自己的理解,接下来继续说上面递归代码的运行与为什么要这么写:

递归是更倾向于逻辑来写代码,所以我们写代码的时候不必过于在乎每一次是怎么运行的,我们只需要将逻辑理顺,怎么运行正确一次就可以了。所以我们就根据逻辑走,首先,题目虽然定义了head指针和反转前链表的头结点,但是并不知道是什么情况,只是定义了。所以我们要先判断,head指针是否为空指针,若为空指针,就说明是空结点,就没必要继续操作,直接返回head指针的值;还有一种情况,我们既然是要反转链表,那么肯定结点数要大于一个结点,所以就有第二个或者的判断:head->next==nullptr,若得出头结点next指针域的值为空,说明后面没有结点了,只有一个头结点,对于这种情况,也是不行的,所以也要返回head的指针值。这就是根据逻辑而得到的代码,而且这也正好是这个递归的结束条件,为后面的回溯做了铺垫。

    好,继续来,直接看我上面图举的例子。对应代码来说的话,刚开始head指针是指向1这个结点的,我们的逻辑是:1->2->3->4->5->NULL我们要变成5->4->3->2->1->NULL的话,我们想知道5这个结点的地址值,好拿来当作反转链表的头结点,然后就能得到5->4->3->2->1->NULL这个样子的结果。所以就有这句话:ListNode* newhead=reverseList(head->next);这句话怎么理解呢,是这样的:我们不断的调用自己这个函数,让head指针不断的前进,当head->next指针指向空的时候,就返回了head指针,这时候head指针指向的是5这个结点。所以此时的newhead,也就是反转链表的第一个头结点,就是5这个结点,但是因为这反转链表现在只有一个头结点,不满足反转的条件,所以还得继续操作,继续回溯。   此时的head指针指向5这个结点,回溯就是返回上一级操作,上一级操作是head指针指向4,而head->next指针指向5这个结点,所以此时4这个结点的指针为head,而5这个结点的地址变为了head->next。我们的目的是让5这个结点指向4这个结点,以达到反转链表的效果,所以有head->next->next=head;这句代码,这是5这个结点指向4这个结点,就是4它的地址值赋给了指向5这个结点的指针,在链表里面,你要想让甲结点指向乙结点,那么就得把乙结点的地址值赋给指向甲结点的指针,这样就好了;也可以从左往右看,既然是5这个结点指向4这个结点,那么就是head->next->next=head;这句代码,表示5这个结点指向4这个结点,这样感觉更直观一些。好,那继续。5这个结点指向4这个结点后,4的后面是没有结点了,至少在这一轮的回溯加操作里面是没有了,所以有head->next=nullptr;这一句代码。好,到了这里,这个代码就解释完了,这就是递归的写法。

二、迭代的写法

//迭代的写法:
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:ListNode* reverseList(ListNode* head) {ListNode* prev=nullptr;// 用于存储已反转部分的链表头
/*创建反转链表的头结点,并且指针此时为nullptr,因为刚开始还没有结点,所以先初始化为nullptr*/ListNode* curr=head;  // 当前处理的节点,从头结点开始
/*这里是定义了一个可操作的指针,方标移动和进行后续操作,刚开始是指向反转前链表的头结点的,反转前链表的头结点地址为head,所以把head这个地址值赋值给curr指针,所以才使得curr指针刚开始就指向反转前链表的头结点*//*定义的前面这两个指针相当于是有用的,一个是用来构建反转链表的,一个是拿来操作的指针,一个是让他有,一个是初始化,不是在操作中所需要不停变换的语言,所以不放入迭代的循环体里面*/while(curr!=nullptr){ListNode* nextTemp=curr->next;// 暂存当前节点的下一个节点,防止丢失
/*每一次迭代的操作都会刚开始存curr->next的地址值,因为后面会操作,会导致数据被覆盖从而会导致错误,所以先存下来,这样后面要用的时候就不会出错了。*/curr->next=prev; // 当前节点指向已反转的部分的头部prev=curr;// prev向前移动一步,指向当前节点curr=nextTemp; // curr向前移动一步,处理下一个节点}return prev; // prev现在是反转后链表的头结点}
};

迭代也画图吧,图如下:

 迭代不同于递归的是,迭代喜欢以操作为主,也就是直来直去的,再麻烦也要想办法弄。所以说某些程度上来说操作会比递归更繁琐具体,但是比递归更好理解。

这个迭代的给我的感觉就是,里面的操作会很繁杂,而且很多还涉及数据留存的问题,为了保证数据使用的正确性,还专门定义了其他的指针变量来存之前的地址值,比如上面代码里面的nextTemp,curr,prev这些指针要么用来存储之前的地址值拿来备用,要么就是操作,每次的操作都要用新定义的指针,尽量不用之前的,按照上面代码来说的话,就是head指针再操作里面几乎没有,尽量不动用他,视为一个参数就好。

 while(curr!=nullptr){ListNode* nextTemp=curr->next;// 暂存当前节点的下一个节点,防止丢失
/*每一次迭代的操作都会刚开始存curr->next的地址值,因为后面会操作,会导致数据被覆盖从而会导致错误,所以先存下来,这样后面要用的时候就不会出错了。*/curr->next=prev; // 当前节点指向已反转的部分的头部prev=curr;// prev向前移动一步,指向当前节点curr=nextTemp; // curr向前移动一步,处理下一个节点}

这部分代码是迭代的重要部分,是迭代的循环体,这得单独讲讲。

 ListNode* nextTemp=curr->next;这一句已经说了,接着下一句。

我们想反转链表,我们要做什么呢?我们先存下了curr->next的地址值,接下来,就让2这个结点指向反转链表这个空链表。而反转链表的头结点我们已经定义了,且指向这个反转链表头结点的是指针prev,所以我们要实现图中圆圈1的操作,即:让2这个结点指向prev,那就是把prev的地址值赋值给curr->next,所以才会有 curr->next=prev;这句代码。    然后此时反转链表已经有了一个头结点2了,那么按照题目的结果示例,肯定2这个结点要指向1结点的。因为我们最后我们要的是反转链表,所以在此时,就要在这个反转链表中看了,以反转链表为主,其他的操作为辅。我要在反转链表中让2结点指向1结点,那我就要知道这两者的地址值,此时2这个结点的地址值就是prev,1结点的地址值就要在前面没有反转之前的链表中找了,找到了,得到1结点的地址为curr,所以就有prev=curr;这一句代码就表示2这个结点指向1结点。从右往左去理解的话就是:因为是2结点指向1结点,所以就要反过来,把1结点的地址值赋值给2结点,所以就有了那一句代码;如果是从左往右的理解的话:那就是2结点指向1结点,2结点的地址从左,给到右的1结点的地址就行了。其实这两种理解方法和上面递归里面说的是差不多的。      好最后一句代码:curr=nextTemp;表示,我进行完了一次操作,那么就该让操作指针curr往前移动一位,所以就把上一次nextTemp的地址值赋值给curr指针,表示继续向前操作。这就是迭代的循环体操作,这是我把他结合实例和代码理解的,是自己的理解,可能会有错的。

ok,解决。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

sgetrf M N is 103040 时报错,这是个bug么 lapack and Openblas the same,修复备忘

1,现象 MN103040时,调用 sgetrf_ 时,无论是 LAPACK 还是 OpenBLAS,都出错: openblas: lapack: 2, 复现代码 出现问题的应该是由于M和N相对数字太大,乘积超出32bit整数的表达范围,…

vulnhub靶机tomato记录

https://www.vulnhub.com/entry/tomato-1,557/ 过程 用nmap对目标主机做全端口扫描,dirb做目录扫描,结果如下: 8888端口开放一个web服务,存在Basic认证,试了爆破无果,sun-answerbook是一个在线文档系统&am…

门店收银系统源码+同城即时零售多商户入驻商城源码

一、我们为什么要开发这个系统? 1. 商户经营现状 “腰尾部”商户,无小程序运营能力;自营私域商城流量渠道单一;无法和线下收银台打通,库存不同步,商品不同步,订单不同步; 2.平台服…

MongoDB学习记录

1、初识Mongo 概述:与关系型数据库不同,MongoDB 的数据以类似于 JSON 格式的二进制文档存储,通常称这种格式为Bson,Bson不仅支持JSON中已有的数据类型,还增加了一些额外的数据类型,例如日期和二进制数据&a…

python爬虫学习记录-请求模块urllib3

(文章内容仅作学习交流使用) urllib3是一个功能强大、条理清晰,用于HTTP客户端的第三方模块 urllib3-发送网络请求 使用urllib3发送网络请求时,需要先创建PoolManager对象,并使用该对象的request方法发送请求&#…

[Spring] Spring AOP

🌸个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 🏵️热门专栏: 🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 🍕 Collection与…

Qt实现类似淘宝商品看板的界面,带有循环翻页以及点击某页跳转的功能

效果如下&#xff1a; #ifndef ModelDashboardGroup_h__ #define ModelDashboardGroup_h__#include <QGridLayout> #include <QLabel> #include <QPushButton> #include <QWidget>#include <QLabel> #include <QWidget> #include <QMou…

扩展【从0制作自己的ros导航小车】C++_ROS_QT5联合编译,简单界面为ROS开发增添交互

从0制作自己的ros导航小车 前言一、环境搭建二、联合编译三、测试 前言 前面已经实现了导航功能&#xff0c;对于之后的一些开发&#xff0c;有交互能力是比较重要的&#xff0c;比如小车上连接一块屏幕&#xff0c;通过屏幕来选择模式&#xff0c;可视化等等。QT是不错的选择…

LVS是什么?以及LVS-NAT以及DR模式实验

目录 NAT LVS LVS集群的类型&#xff1a; LVS-NAT模式实验 环境准备&#xff1a; 实验步骤&#xff1a; LVS-DR模式实验 题目&#xff1a; 环境准备&#xff1a; 实验步骤&#xff1a; LVS-防火墙标签解决轮询调度问题 环境准备&#xff1a; 实验步骤&#xff1…

智界S7 小鹏P7 G3 G3i P5 G9 P7i G6 X9维修手册和电路图线路图接线资料更新

汽修帮手资料库提供各大厂家车型维修手册、电路图、新车特征、车身钣金维修数据、全车拆装、扭力、发动机大修、发动机正时、保养、电路图、针脚定义、模块传感器、保险丝盒图解对照表位置等&#xff0c;并长期保持高频率资料更新&#xff01; 覆盖车型2020-2024年智界S7 小鹏…

在VScode中导入conda环境的记录【原创】

今天在vscode编辑器中运行一个python代码&#xff0c;发现终端可以运行&#xff0c;但是编辑器中点击Run会显示缺包&#xff0c;但是python包明明是有的&#xff0c;在自己的conda环境中。后来发现&#xff0c;是vscode没有发现我自己创建的conda环境&#xff0c;在vscode中导入…

51单片机个人学习笔记16(红外遥控)

前言 本篇文章属于STC89C52单片机&#xff08;以下简称单片机&#xff09;的学习笔记&#xff0c;来源于B站教学视频。下面是这位up主的视频链接。本文为个人学习笔记&#xff0c;只能做参考&#xff0c;细节方面建议观看视频&#xff0c;肯定受益匪浅。 [1-1] 课程简介_哔哩…

Java封装原生ES

文章目录 &#x1f31e; Sun Frame&#xff1a;SpringBoot 的轻量级开发框架&#xff08;个人开源项目推荐&#xff09;&#x1f31f; 亮点功能&#x1f4e6; spring cloud模块概览常用工具 &#x1f517; 更多信息1.spring-data-es操作ES1.引入依赖2.application.yml配置uris3…

高频焊接设备配电系统无源滤波系统的设计

1、高频焊机系统谐波状况简介 变压器容量&#xff1a;S11-M-1600/10KVA&#xff08;105%&#xff09;/0.4KV 短路阻抗&#xff1a;3.9% 谐波负载情况&#xff1a;一台600KW高频焊接设备 型号&#xff1a;GGP600-0.3-HC 输入电压&#xff1a;380V 输出电压&#xff1a;0…

【Python机器学习】回归——示例:预测乐高玩具套装的价格

用回归法预测乐高套装价格的基本步骤&#xff1a; 1、收集数据&#xff1a;用Google Shopping的API收集到的数据 2、准备数据&#xff1a;从返回的JSON数据中抽取价格 3、分析算法&#xff1a;可视化并观察数据 4、训练算法&#xff1a;构建不同的模型&#xff0c;采用逐步线性…

操作ArkTS页面跳转及路由相关心得

本文为JS老狗原创。 当前端不得不关注的点&#xff1a;路由&#xff0c;今天聊一聊鸿蒙相关的一点心得。 总体上套路不意外&#xff0c;基本就是&#xff08;尤其是Web&#xff09;前端那些事&#xff1a;维护路由表、跳转带参数、历史堆栈操作&#xff0c;等等。 历史原因&…

设计模式20-备忘录模式

设计模式20-备忘录 动机定义与结构定义结构 C代码推导优缺点应用场景总结备忘录模式和序列化备忘录模式1. **动机**2. **实现方式**3. **应用场景**4. **优点**5. **缺点** 序列化1. **动机**2. **实现方式**3. **应用场景**4. **优点**5. **缺点** 对比总结 动机 在软件构建过…

云服务器和物理服务器的优缺点对比

云服务器优点在于灵活性强、成本效益高、易于扩展且支持全球化部署&#xff1b;缺点则包括安全性与可控性相对较弱&#xff0c;性能可能受限&#xff0c;以及存在服务中断风险。物理服务器则以其高性能、高稳定性、强安全性和完全可控性著称&#xff0c;但成本较高、扩展性受限…

鸿蒙OS ArkTS 省市县级联选择框,封装组件

背景&#xff1a; 公司现在要开发纯血鸿蒙版本APP&#xff0c;我被抽调过来做点功能。现在要做一个省市县级联选择框&#xff0c;并且要封装为组件&#xff0c;供其他页面模块使用。 效果图&#xff1a; 难点&#xff1a; 1. 现在官方文档上只是查到了TextPicker组件是可以做…

Vue3+setup使用vuemap/vue-amap实现地图相关操作

首先要下载依赖并且引入 npm安装 // 安装核心库 npm install vuemap/vue-amap --save// 安装loca库 npm install vuemap/vue-amap-loca --save// 安装扩展库 npm install vuemap/vue-amap-extra --save cdn <script src"https://cdn.jsdelivr.net/npm/vuemap/vue-a…