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

news2024/11/25 14:58:03

反转链表+递归求解

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

206. 反转链表

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

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

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

代码如下(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前驱节点初始化为null
        ListNode* 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;
        //返回的新头节点是整个链表反转后的头节点,因此一直返回newhead
        return 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_pre
        for(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->next
        leftNode->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;  //数组用于存储链表节点的val
        ListNode *currNode = head;
        //遍历链表节点,并保存各个节点的val
        while(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) {
    	//如果只有一个节点直接返回true
        if(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;  
        }
        //两段链表节点逐个对比val
        while(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){
            //后面已经有节点和前面的不相等,中间一截不用比较了直接向上返回false
            if(!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.coloradmin.cn/o/912784.html

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

相关文章

python接口自动化之如何使用requests库发送http请求

前言 今天笔者想和大家来聊聊python接口自动化如何使用requests库发送http请求&#xff0c;废话呢笔者就不多说了&#xff0c;直接进入正题。 一、requests库 什么是Requests &#xff1f;Requests 是⽤Python语⾔编写&#xff0c;基于urllib&#xff0c;采⽤Apache2 Licens…

大数据 算法

什么是大数据 大数据是指数据量巨大、类型繁多、处理速度快的数据集合。这些数据集合通常包括结构化数据&#xff08;如数据库中的表格数据&#xff09;、半结构化数据&#xff08;如XML文件&#xff09;和非结构化数据&#xff08;如文本、音频和视频文件&#xff09;。大数据…

跨境电商独立站创业路线?怎么做外贸网站?

跨境电商独立站创业需要做哪些准备?创建国际贸易自主网站的流程&#xff1f; 跨境电商独立站创业&#xff0c;正逐渐成为许多创业者的首选。这一领域蕴藏着巨大的商机&#xff0c;同时也需要创业者具备坚定的决心和深入的市场洞察力。下面将为您详细介绍一条成功的跨境电商独…

科技云报道:云计算下半场,公有云市场生变,私有云风景独好

科技云报道原创。 大数据、云计算、人工智能&#xff0c;组成了恢弘的万亿级科技市场。这三个领域&#xff0c;无论远观近观&#xff0c;都如此性感和魅力&#xff0c;让一代又一代创业者为之杀伐攻略。 然而高手过招往往一瞬之间便已胜负知晓&#xff0c;云计算市场的巨幕甫…

网络安全应急管理与技术实践:应对不断演化的网络威胁

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 当谈论网络安全应急管理…

基于web的服装商城系统java网上购物商店jsp源代码mysql

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 基于web的服装商城系统 系统有1权限&#xff1a;前台…

优化学习体验的在线考试系统

随着互联网的发展&#xff0c;在线教育逐渐成为学习的主要方式之一。在线考试系统作为在线教育的重要组成部分&#xff0c;对于学习者提供了更为便捷和灵活的学习方式。但是&#xff0c;如何优化学习体验&#xff0c;提高学习效果&#xff0c;仍然是在线考试系统需要解决的问题…

等级查询发布助手

考试成绩的发布是学校教学工作中的一项重要任务&#xff0c;传统的手工录入、统计和发布成绩的方式既耗时又容易出错。为了提高老师的工作效率和准确性&#xff0c;推荐老师们试一试易查分考试等级发布系统。 易查分是一个查询/发布发布平台 1. 快速高效&#xff1a;老师只需将…

『论文精读』FastViT(ICCV 2023,Apple开源)论文解读

『论文精读』FastViT(ICCV 2023&#xff0c;Apple开源)论文解读 文章目录 一. FastViT简介二. 模型架构2.1. Stage 的内部架构2.2. Stem 的结构2.3. Patch Embedding 的架构2.4. 位置编码 三. 参考文献 论文下载链接&#xff1a;https://arxiv.org/pdf/2303.14189.pdf论文代码…

【网络层协议】ARP攻击与欺骗常见的手段以及工作原理

个人主页&#xff1a;insist--个人主页​​​​​​ 本文专栏&#xff1a;网络基础——带你走进网络世界 本专栏会持续更新网络基础知识&#xff0c;希望大家多多支持&#xff0c;让我们一起探索这个神奇而广阔的网络世界。 目录 一、ARP攻击的常见手段 第一种&#xff1a;IP…

ardupilot开发 --- 仿真篇

环境 安装wsl2&#xff0c;win11自带wsl&#xff0c;win10需要安装&#xff1b;git clone ardupilot 源码&#xff1b;安装 Linux下的build环境&#xff1b;安装 flightgear&#xff08;非必须&#xff09; sudo apt-get install flightgearbuild 想要仿真的载具类型&#xff…

shell 02(shell变量)

一、shell变量 变量用于存储管理临时的数据&#xff0c; 这些数据都是在运行内存中的。 1.1 变量类型 1.1.1 系统环境变量 是系统提供的共享变量.是linux系统加载Shell的配置文件中定义的变量共享给所有的Shell程序使用 shell的配置文件分类 全局配置文件 /etc/profile /et…

外贸独立站怎么做内容营销?外贸独立站内容营销的步骤e6zzseo

在如今全球化的商业环境下&#xff0c;外贸独立站已成为吸引国际客户、拓展市场份额的重要工具。然而&#xff0c;仅仅拥有一个外贸独立站还不足以保证成功&#xff0c;内容营销在其中扮演着至关重要的角色。本文将介绍如何在外贸独立站上实施内容营销&#xff0c;以及实现有效…

交换的综合实验

目录 ​编辑 一&#xff0c;交换层面的配置 1.channel 2.vlan 3.trunk 二&#xff0c;配置路由 R1 R2 LW1 LW2 三&#xff0c;OSPF 四&#xff0c;能够访问外网操作 一&#xff0c;交换层面的配置 配置顺序 channel vlan trunk stp svi vrrp dhcp 1.channel LW…

用了好几年的IDEA主题及配置,拿去吧不谢。

前言 最近这几年一直用一套IDEA的主题及配置&#xff0c;分享给各位&#xff0c;如果符合你的口味&#xff0c;可以下载了玩玩。 我个人是非常喜欢的&#xff0c;不管是观感还是敲代码都很爽的。 附上一张代码的主题色&#xff0c;大概就是这样子&#xff0c;我个人喜欢清爽的白…

防火墙+路由模式部署

一、防火墙 防火墙最主要功能是提供访问控制能力 防火墙默认管理口为ge0/0&#xff08;部分型号有专门的MGT口&#xff09;&#xff0c;管理地址为https://192.168.1.250&#xff0c;默认管理口只开启了https和ping。登录防火墙串口&#xff0c;波特率为9600&#xff0c;默认…

Anaconda常用指令

导语 Python是现今较为流行的一门编程语言&#xff0c;解释性强&#xff0c;语法灵活&#xff0c;而且具有强大的函数库&#xff0c;这些函数全部都被封装成一个个的模块(Module)&#xff0c;也称作包&#xff0c;所以我们在使用的时候需要进行导入(import)&#xff0c;但是所有…

【数据结构】实现带头双向循环链表

目录 前言&#xff1a;一、介绍带头双向循环链表1.带头双向循环链表的结构2.带头双向循环链表的功能 二、实现带头双向循环链表1.创建节点的结构2.函数的声明2.函数的实现&#xff08;1&#xff09;创建一个新节点&#xff08;2&#xff09;初始化哨兵位&#xff08;带头&#…

wx原生微信小程序入门常用总结

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、定义值和修改值1、定义值2、修改值&#xff08;1&#xff09;代码&#xff08;2&#xff09;代码说明&#xff08;3&#xff09;注意点 二、点击事件三、微…

油耳朵适合戴什么类型耳机好,适合油耳的无线耳机推荐

传说中的骨传导耳机&#xff0c;相信大家都不陌生吧&#xff01;近年来&#xff0c;这种耳机以其不需要插入耳朵、不会堵塞耳道的特点&#xff0c;在耳机圈内迅速崛起。然而&#xff0c;还有一些人对骨传导耳机望而却步&#xff0c;不知道如何选择适合自己的产品。作为一位骨传…