链表经典题目: 回文链表leetcode 234

本文探讨LeetCode 234题——如何判断一个单向链表是否为回文。通过思路梳理、代码实现和优化,介绍如何在O(n)时间和O(1)空间复杂度内解决此问题,同时讲解链表原地反转的面试重点。
摘要由CSDN通过智能技术生成

一、原题回放

LeetCode 234:给定一个单向链表,判断它是否为回文链表并返回true或false。
例如下图这样一个链表,它的正序为“122”,反序为“221”,正序和反序不相同,因此我们认为其不是回文链表。
在这里插入图片描述
下图所示的链表正序和反序都是“1221”, 则我们认为它是回文链表。
在这里插入图片描述
我们需要注意的是,当链表中只有一个节点时,它的正序和反序都是第一个节点本身,当链表中不存在任何节点时,它的正序和反序都是空,所以只有一个节点的链表和没有任何节点的链表一定是回文链表。

二、思路梳理

从题目中我们知道,回文链表就是正序和反序相同的链表,因此我们可以采用依次比较链表中的第1个和第倒数第1个元素、第2个和第倒数第2个元素、第3个和第倒数第3个元素……直到比较完所有元素,只要在比较过程中有一个元素不同就认为它不是回文链表。
在这里插入图片描述
不过我们的链表单向链表,最后一个元素要遍历整个链表才能获得,在获取倒数第2个元素时又要将链表遍历一遍,如果我们的算法每次都从链表中直接遍历来获取元素进行比较的话,每次查找尾部元素的时候都要遍历整个链表,算法的时间复杂度将达到O(n^2),效率十分低下。因此我们有一个比较直观的思路,先将链表中的所有元素复制到数组中,然后在数组中取出元素进行比较,由于在数组中可以使用脚标直接获取数据,算法效率将大大提升。
在这里插入图片描述
根据上述思路我们得到如下伪代码:
在这里插入图片描述
原问题被分解为两个小问题:

  1. 遍历链表,计算链表长度并找到中点
  2. 反转链表的后半部分
  3. 依次比较两个链表元素是否全部相同,如果所有元素都相同则认为它是回文链表,否则它不是回文链表

小伙们伴可以先根据伪代码自行尝试完成一下代码。

三、代码实现

将之前的伪代码进行具体实现可以得到如下代码:
在这里插入图片描述在这里插入图片描述
这样的一个算法在执行过程中对整个链表遍历的一遍,对和链表等长的list同样也遍历了一遍,因此它的时间复杂度是O(n);同时我们为了将链表保存到list,使用了和链表元素数量相同的list,因此它的空间复杂度也是O(n)。
在编码过程中,还有几个问题需要大家注意:

  1. 在依次比较list中头尾元素的时候(第16行),因为是头尾同时向中间比较,头尾各比较一般就已将将整个list全部比较完了,因此这里对长度len进行了除以2的操作。

  2. 由于list中的元素的脚标是从0开始,因此获取数组尾部元素的时候要注意减1(第17行),否则会出现数组越界错误。

  3. 在代码的最开始,还要记得判断head为null的特殊情况(第3行)。

四、更优化思路

在上面的解题思路中,我们为了解决获取单向链表获取尾部元素效率低下的问题,将链表中的数据全部复制到了list中,这样没有利用到链表的特性,也使用了更多的内存空间。那我们有没有办法利用链表的特性对当前链表进行一些修改,做到不需要申请额外空间也可以高效率的获取到链表尾部的元素呢?
我们可能会想,如果链表既可以正向依次遍历的同时也可以反向依次遍历就好了。是的,要做到这一点,就需要让链表后一节点的指针指向前一节点,我们通过将链表进行原地反转来做到,但是,我们应该反转全部的链表吗?如果将全部的链表进行反转,虽然获取原链表尾部的节点十分方便,但我们就无法高效的获取头部的节点了。其实我们只需要将链表从中间一分为二,将链表的后半部分进行反转。
在这里插入图片描述
根据上述思路我们得到如下伪代码:
在这里插入图片描述
原问题被分解为三个小问题:

  1. 遍历链表,计算链表长度并找到中点
  2. 反转链表的后半部分
  3. 依次比较两个链表元素是否全部相同,如果所有元素都相同则认为它是回文链表,否则它不是回文链表。

小伙们伴可以先根据伪代码自行尝试完成一下代码。

五、代码实现

将上面的伪代码进行具体实现可以得到如下代码:
在这里插入图片描述
在这里插入图片描述代码的第23行至第31行使用了一个临时节点next对链表进行了原地反转,对于基础没那么强的同学,这里要仔细理解一下。
算法在执行过程中对链表遍历了四次,因此它的时间复杂度是O(n);同时因为我们没有申请额外的空间,因此它的空间复杂度是O(1)。
在编码过程中,还有几个问题需要大家注意:

  1. 遍历至链表中点时,因为mid以第1个节点head为初始值,因此循环里的循环变量i的初始值要设为1(第18行)。
  2. 我们将原来的一整个链表分成了前后两个链表,当链表节点数为奇数时,如果链表是回文链表,那么最终间的节点是不需要再和其它节点进行比较的。比如下面这个回文链表中,我们只需要比较头尾的两个节点,最中间的节点“3”就不需要进行比较了。
    在这里插入图片描述
    由于后半部分的链表将比前半部分多一个节点(具体原因为代码第16行,len/2被自动取整),因此在对两个链表进行比对时,需要以前半部分链表的结束为准(第34行),如果这里写pre!=null的话,当链表节点数为奇数时将会产生空指针错误。
  3. 和前一个思路一样,在代码的最开始,同样需要记得判断head为null的特殊情况(第3行)。

六、Python实现

将上面的C++代码改写为Python代码:
在这里插入图片描述在这里插入图片描述

七、面试重要知识点提炼

第二个思路中提到的链表原地反转(代码第23行至第31行)是面试的常见考点,大家一定要理解透彻,并多加练习巩固。

八、进一步思考

在第二个思路中,我们通过对链表遍历两次来获得了链表的中点。其中,第一次遍历时计算了链表的长度和中点的位置,第二次遍历时将指针移动到中点。不过,有没有只遍历一次就可以获得链表的方法呢?

九、相关题目

LeetCode 206. 反转链表[https://leetcode-cn.com/problems/reverse-linked-list/]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值