【leetcode 力扣刷题】反转链表+递归求解

反转链表+递归求解

  • 206. 反转链表
    • 解法①:取下一个节点在当前头节点前插入
    • 解法②:反转每个节点next的指向
    • 解法③:递归
  • 92.反转链表Ⅱ
    • 反转left到right间节点的next指向
  • 234.回文链表
    • 解法①:将链表元素存在数组中,在数组上判断回文
    • 解法②:在链表上反转前半部分链表,和后半部分对比
    • 解法③:==递归==

206. 反转链表

题目链接:206.反转链表
题目内容:
在这里插入图片描述
理解题意:没有特殊要求,就是把链表反转,相当于从之前的末尾指向开头。

解法①:取下一个节点在当前头节点前插入

第一种方法最容易想到,从前往后遍历链表的同时,每次从原链表中取下当前节点,插入到新链表的开头。 这里的新链表,实际就是该节点之前所有节点已经反转后构成的链表,过程如下:
5在这里插入图片描述

代码如下(C++):

//依次取出每个节点,并将其插入在新链表的头部
class Solution {
public:ListNode* reverseList(ListNode* head) {//如果链表为空直接返回空指针if(!head)return nullptr;ListNode *currNode = head;//初始化新链表,为空ListNode *newhead = nullptr;while(currNode != nullptr){   ListNode *tmp = currNode->next;// currNode插入在新链表的头部currNode->next = newhead; newhead = currNode;//currNode移到下一个节点currNode = tmp;         }//返回新链表return newhead;}
};  

解法②:反转每个节点next的指向

假设原链表中一前一后两个节点:preNode和currNode,指向是preNode->next = currNode;反转后链表中这俩节点的指向是currNode->next = preNode。既然如此,那么可以在遍历链表中节点的时候【当前节点就用currNode】,直接改变其指针域:
在这里插入图片描述
代码如下(C++):

//遍历链表每个节点,将next改变成指向前面;
class Solution {
public:ListNode* reverseList(ListNode* head) {ListNode* prev = NULL;  //prev前驱节点初始化为nullListNode* currNode = head;  //当前节点初始化为头节点while(currNode){ListNode* tmp = currNode->next;//改变当前节点指针,指向其前驱节点currNode->next = prev;//prev和currNode都向后移动,遍历链表节点prev = currNode;currNode = tmp;}return prev; //最后prev就是新的head,currNode已经为null了}
};

本质上解法①和解法②是一样的,只是从两个角度去理解“反转”。

解法③:递归

从前往后遍历链表节点,并使用递归方法的时候,要到最后一个节点才开始往前返回,那么就是先反转后半截链表,再向前返回到当前的节点,再对当前节点处理。假设链表有k个节点:

  • 递归到第k个节点的时候,递归终止,并且这最后一个节点就是整个链表反转后的头节点,直接返回该节点地址;
  • m+1个节点作为后半截已经反转后的链表的尾节点,其next指向null;现在将m+1和m个节点连接起来,m+1的next指向m;m+1个节点是后半截反转后的尾节点,怎么找到m+1个节点呢? 【对于当前第m个节点,其next依然指向m+1个节点,因此直接currNode->next->next = currNode,使m+1个节点的next指向当前第m个节点】;
  • m现在作为m-1之后剩余链表反转后的尾节点,其next应该指向null;
  • 到第m个节点的时候,m+1~k个节点已经反转了,并返回了新的头节点;即便是加上第m个节点并反转,第m个节点也是加在反转后链表的尾部,因此上一步返回的新头节点是整个链表反转后头节点,因此要一直向前返回这个地址;

整个过程如图所示: 在这里插入图片描述

代码如下(C++):

class Solution{
public:ListNode* reverseList(ListNode* head) {//如果链表为空,直接返回//如果已经遍历到最后一个节点,最后一个节点就是反转后的头节点,直接返回if(head == nullptr || head->next == nullptr)return head;//当前head之后的链表已经反转了,并返回反转后的新头节点指针     ListNode *newhead = reverseList(head->next);//将head->next的next指针指向当前head,反转head->next->next = head;//断开当前head和head->next之间原本的连接head->next = nullptr;//返回的新头节点是整个链表反转后的头节点,因此一直返回newheadreturn newhead;}    
};

92.反转链表Ⅱ

题目链接:92.反转链表Ⅱ
题目内容:
在这里插入图片描述
理解题意:在对整个链表反转的基础上,增加了限制条件——只反转给定的left~right位置间的节点。 完成left~right的反转后,还需要让left之前的节点left_pre->next指向right;还需要让left->next指向right->next
以下解法为一遍访问链表完成left~right之间的节点的反转:

反转left到right间节点的next指向

整个过程分为以下几步:

  • 需要记录的节点有left、right、left前面一个节点left_pre和right后面一个节点right->next;
  • 先遍历链表找到left_pre和left;
  • 从left开始到right,用反转链表题目的解法,反转这一段链表;用到pre和currNode两个变量保存当前要改变指向的节点、以及之前的一个节点;
  • 上一步循环结束时,pre就是right,currNode就是right->next; 此时建立新连接:left_pre->next = right【即pre】,left->next = right->next【即currNode】; 得到完整链表;

上述过程为一次遍历链表,在遍历的过程中改变left~right间的指向。全部代码如下(C++):

class Solution {
public:ListNode* reverseBetween(ListNode* head, int left, int right) {//如果left和right相等,没有节点要反向if(left == right) return head;//存left和left前面一个节点的地址ListNode *leftNode = head, *left_prev = NULL;//定位到leftNode,并保存left_prefor(int i=1; i<left && leftNode; i++ ){left_prev = leftNode;leftNode = leftNode->next;} //left~right间反向时用到的变量  ListNode *prev, *currNode;   //当前节点和当前节点的前一个节点    prev = leftNode;currNode = prev->next;while(currNode && left < right){ListNode* tmp = currNode->next;//反转next指向currNode->next = prev;//pre和currNode都向前移动,遍历left~right间节点prev = currNode;currNode = tmp;left++;}  //循环结束后pre是right节点,currNode是right->next节点//左边节点leftNode指向right->nextleftNode->next = currNode;//判断leftnode是不是头节点if(left_prev  != NULL) //如果不是,left前面一个节点指向right节点left_prev->next = prev;else //如果是,新的头节点就是right节点head =  prev;        return head;}
};

234.回文链表

题目链接:203.回文链表
题目内容:
在这里插入图片描述
理解题意:实际就是判断是不是回文【回文数:从前往后、从后往前是一样的,比如0123210;判断:两个指针一个开头,一个结尾,逐个对比,全相等就是回文】,只是换成了在链表上判断。 但是由于链表只能向next一个方向遍历,不像数组、string等可以用下标index去定位。因此有两种解法:

  • 遍历链表,并且把链表各个节点的val按照顺序存在vector里面,然后在vector上比较;
  • 直接在链表上比较;

直接在链表上进行比较又有两种方法:

  • 遍历链表的同时,用上面的反转链表方法,把前半部分链表反转;反转后的前半部分链表和后半部分链表的节点逐个对比,val值都一样即为回文;
  • 递归求解:因为递归到最后一个节点才递归终止,能够知道当前的节点的val;一开始的head还在开头,就能实现首尾比较;一旦不相等,其他节点就不用比较了,直接向前返回false;如果相等,后面的节点向前返回到前面一个节点进行操作,前面节点需要向后移动;

解法①:将链表元素存在数组中,在数组上判断回文

这个方法最好理解,也没有什么难度,先遍历链表取出各个节点的val,按原顺序存在vector中,在vector上实现回文判断,需要额外的空间……
代码如下(C++):

class Solution {
public:bool isPalindrome(ListNode* head) {vector<int> val;  //数组用于存储链表节点的valListNode *currNode = head;//遍历链表节点,并保存各个节点的valwhile(currNode){val.emplace_back(currNode->val);currNode = currNode->next;}//双指针,判断数组是否是回文的for(int i = 0, j = val.size() - 1; i < j ; i++,j--){//一旦有节点不相等,就不是回文if(val[i] != val[j])return false;}return true;}};

解法②:在链表上反转前半部分链表,和后半部分对比

这个方法是一次遍历链表,遍历的过程中,同时反转链表,这个反转结束的地方是链表的中间点; 从这个中间点开始,用一个指针逐个向前访问前面一段链表的节点,再用一个指针逐个向后访问后面一段链表的节点;并比较节点的val,判断是否是回文,过程如下所示:
在这里插入图片描述
这里有个问题,就是如何找到链表的中间节点:用一个slow指针,一个fast指针,初始slow和fast都为head【没有附加头结点时】,每次slow向后移动一个slow = slow->next,但是fast向后移动两个fast = fast->next->next。当fast->next =null的时候【有偶数个节点】或fast->next->next = null的时候【有奇数个节点】终止,此时的slow就定位在中间节点。
代码实现(C++):

class Solution {
public:bool isPalindrome(ListNode* head) {//如果只有一个节点直接返回trueif(head->next== nullptr)return true;//用slow、fast来寻找链表的中间点,prev是slow前面一个节点,辅助完成反转操作       ListNode *slow = head,*fast = head, *prev = NULL;//找到中间节点,并同时反转前半段链表while(fast->next && fast->next->next){  fast = fast->next->next;       ListNode* tmp = slow->next;slow->next = prev;prev = slow;slow = tmp;                       } //上述循环结束时,slow是(n+1)/2节点//比如5个节点,slow在第3个节点;6个节点,slow在第3个节点//且此时slow指向是原始的指向ListNode *right_head, *left_head; //左右两段一段向前,一段向后链表的头节点right_head = slow->next;  //右边头节点就是slow->next//偶数个节点,slow需要反转连接if(fast->next){            slow->next = prev;left_head = slow;}//奇数个节点,slow不需要反转连接else{ left_head = prev;  }//两段链表节点逐个对比valwhile(left_head!=nullptr && right_head!=nullptr){if(left_head->val != right_head->val)return false;left_head = left_head->next;right_head = right_head->next;}return true;}
};

其中奇数个节点、偶数个节点需要分开讨论,写代码的时候要区分开。

解法③:递归

这里的递归求解参考的力扣官方题解。因为递归到最后一个节点才递归终止,能够知道当前的节点的val;一开始的frontNode还在开头,就能实现首尾比较;一旦不相等,其他节点就不用比较了,直接向前返回false;如果相等,后面的节点向前返回到前面一个节点进行操作,前面节点需要向后移动;
递归的时候需要一个指针,递归到最后向前返回;那么还需要一个外部指针,递归返回后,它向后移动。
代码实现(C++):

class Solution {
public:ListNode* frontNode; //需要定义一个全局的变量bool recursivelyCheck(ListNode *currNode){if(currNode){//后面已经有节点和前面的不相等,中间一截不用比较了直接向上返回falseif(!recursivelyCheck(currNode->next))return false;//对比当前元素与前面对应元素是否一样if(currNode->val != frontNode->val)return false;//将前面元素向后面移动一个frontNode = frontNode->next;}return true;}bool isPalindrome(ListNode* head) {frontNode = head; return recursivelyCheck(head);}
};

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

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

相关文章

解决idea登录github copilot报错问题

试了好多方案都没用&#xff0c;但是这个有用&#xff0c; 打开idea-help-edit custonm vm options 然后在这个文件里面输入 -Dcopilot.agent.disabledtrue再打开 https://github.com/settings/copilot 把这个设置成allow&#xff0c;然后重新尝试登录copilot就行就行 解决方…

【JVM 内存结构 | 程序计数器】

内存结构 前言简介程序计数器定义作用特点示例应用场景 主页传送门&#xff1a;&#x1f4c0; 传送 前言 Java 虚拟机的内存空间由 堆、栈、方法区、程序计数器和本地方法栈五部分组成。 简介 JVM&#xff08;Java Virtual Machine&#xff09;内存结构包括以下几个部分&#…

countDown+react+hook

道阻且长&#xff0c;行而不辍&#xff0c;未来可期 知识点一&#xff1a; new Date().getTime()可以得到得到1970年01月1日0点零分以来的毫秒数。单位是毫秒 new Date().getTime()/1000获取秒数1分钟60秒&#xff0c;1小时60分钟1hour:60*60>单位是秒 60*60*1000>单位…

Android 9.0 Vold挂载流程解析(下)

Android 9.0 Vold挂载流程解析&#xff08;上&#xff09; 前言 上一篇介绍了Android 文件系统中Vold挂载机制的总体框架&#xff0c;我们分析了vod进程的main.cpp.接下来我们分析下存储卡挂载和卸载的流程。 存储卡挂载 在上篇文章文章提到&#xff0c;监听驱动层挂载和卸…

opencv简单使用

cv2库安装&#xff0c; conda install opencv-python注意cv2使用时&#xff0c;路径不能有中文。&#xff08;不然会一直’None’ _ update # 处理中文路径问题 def cv_imread(file_path): #使用之前需要导入numpy、cv2库&#xff0c;file_path为包含中文的路径return cv2.imd…

蓝蓝设计ui设计公司作品--泛亚高科-光伏电站控制系统界面设计

泛亚高科(北京)科技有限公司&#xff08;以下简称“泛亚高科”&#xff09;&#xff0c;一个以实时监控、高精度数值计算为基础的科技公司&#xff0c; 自成立以来&#xff0c;组成了以博士、硕士为核心的技术团队&#xff0c;整合了华北电力大学等高校资源&#xff0c;凭借在电…

MFC——base编码和json数据

目录 1. JSON是什么 2. base64是什么 Base64是一种编解码算法 1. JSON是什么 JSON 是一种数据格式。采用完全独立于语言的文本格式, 因为易读, 易写, 易解析的特性成为理想的数据交换语言。主要有三种类型的值:简单值(字符串, 数字, 布尔, null), 对象, 数组。 长这样的数…

2023前端面试笔记 —— HTML5

系列文章目录 内容链接2023前端面试笔记HTML5 文章目录 系列文章目录前言一、HTML 文件中的 DOCTYPE 是什么作用二、HTML、XML、XHTML 之间有什么区别三、前缀为 data- 开头的元素属性是什么四、谈谈你对 HTML 语义化的理解五、HTML5 对比 HTML4 有哪些不同之处六、meta 标签有…

Java实现一个简单的图书管理系统(内有源码)

简介 哈喽哈喽大家好啊&#xff0c;之前作者也是讲了Java不少的知识点了&#xff0c;为了巩固之前的知识点再为了让我们深入Java面向对象这一基本特性&#xff0c;就让我们完成一个图书管理系统的小项目吧。 项目简介&#xff1a;通过管理员和普通用户的两种操作界面&#xff0…

在Ubuntu上安装和设置RabbitMQ服务器,轻松实现外部远程访问

文章目录 前言1.安装erlang 语言2.安装rabbitMQ3. 内网穿透3.1 安装cpolar内网穿透(支持一键自动安装脚本)3.2 创建HTTP隧道 4. 公网远程连接5.固定公网TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址 前言 RabbitMQ是一个在 AMQP(高级消息队列协议)基…

【Python原创毕设|课设】基于Python Flask的上海美食信息与可视化宣传网站项目-文末附下载方式以及往届优秀论文,原创项目其他均为抄袭

基于Python Flask的上海美食信息与可视化宣传网站&#xff08;获取方式访问文末官网&#xff09; 一、项目简介二、开发环境三、项目技术四、功能结构五、运行截图六、功能实现七、数据库设计八、源码获取 一、项目简介 随着大数据和人工智能技术的迅速发展&#xff0c;我们设…

Git如何操作本地分支仓库?

基本使用TortoiseGit 操作本地仓库(分支) 分支的概念 几乎所有的版本控制系统都以某种形式支持分支。 使用分支意味着你可以把你的工作从开发主线上分离开来&#xff0c;避免影响开发主线。多线程开发,可以同时开启多个任务的开发&#xff0c;多个任务之间互不影响。 为何要…

【洛谷】P1678 烦恼的高考志愿

原题链接&#xff1a;https://www.luogu.com.cn/problem/P1678 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 将每个学校的分数线用sort()升序排序&#xff0c;再二分查找每个学校的分数线&#xff0c;通过二分找到每个同学估分附近的分数线。 最后…

【接口优化方案解决】

文章目录 前言1、批量插入或者查询数据库2、异步思想 耗时操作&#xff0c;考虑放到异步3、空间换时间思想&#xff1a;恰当使用缓存。4. 预取思想&#xff1a;提前初始化到缓存5、借用线程池6. 事件回调思想&#xff1a;拒绝阻塞等待。7、锁粒度避免过粗8、切换存储方式&#…

Spring Clould 网关 - Gateway

视频地址&#xff1a;微服务&#xff08;SpringCloudRabbitMQDockerRedis搜索分布式&#xff09; Gateway网关-网关作用介绍&#xff08;P35&#xff09; Spring Cloud Gateway 是 Spring Cloud 的一个全新项目&#xff0c;该项目是基于 Spring 5.0&#xff0c;Spring Boot 2…

java八股文面试[JVM]——JVM内存结构

参考&#xff1a; JVM学习笔记&#xff08;一&#xff09;_卷心菜不卷Iris的博客-CSDN博客 JVM是运行在操作系统之上的&#xff0c;它与硬件没有直接的交互 JVM内存结构&#xff1a; 方法区&#xff1a;存储已被虚拟机加载的类元数据信息(元空间) 堆&#xff1a;存放对象实…

LVS集群 (NET模式搭建)

目录 一、集群概述 一、负载均衡技术类型 二、负载均衡实现方式 二、LVS集群结构 一、三层结构 二、架构对象 三、LVS工作模式 四、LVS负载均衡算法 一、静态负载均衡 二、动态负载均衡 五、ipvsadm命令详解 六、搭建实验流程 一、首先打开三台虚拟机 二、…

村口的人家排放污水,污水浸染了整个村子,怎么办

从前有一个很不错的村子里&#xff0c;村子里有很多户人家&#xff0c;随着生活水平越来越好&#xff0c;房子也修起来了&#xff0c;柏油马路也宽敞了&#xff0c;大家进出村子&#xff0c;都要走那条马路&#xff0c;要不就出不去。 目录 1. 修厕所 2. 村口的日家 3. 告诉…

【C语言】动态内存管理,详细!!!

文章目录 前言一、为什么存在动态内存分配二、动态内存开辟函数的介绍1.malloc2.calloc3.realloc4.free 三、动态内存开辟中的常见错误1.误对NULL进行解引用操作2.对于动态开辟的空间进行了越界访问3.对于非动态开辟的内存进行了free操作4.只free掉动态开辟内存的一部分5.多次f…

笔记:transformer系列

1、和其他网络的比较 自注意力机制适合处理长文本&#xff0c;并行度好&#xff0c;在GPU上&#xff0c;CNN和Self-attention性能差不多&#xff0c;在TPU&#xff08;Tensor Processing Uni&#xff09;效果更好。 总结&#xff1a; 自注意力池化层将当做key,value,query来…