假设以带头结点的循环链表表示队列_关于反转链表,看这一篇就够了!

本期例题:LeetCode 206 - Reverse Linked List[1](Easy)

反转一个单链表。

示例:

  • 输入: 1->2->3->4->5->NULL

  • 输出: 5->4->3->2->1->NULL

反转链表这道题是我在阿里的面试中遇到的题目。它本身也是单链表题目中非常典型的一道,不少题目的解法以反转链表为基础。这篇文章将会包含:

  • 链表类题目的注意点
  • 链表遍历的基本框架
  • 本期例题:反转链表的解法
  • 相关题目

链表类题目的注意点

在面试中涉及到的链表类题目,一定都是单链表。虽然实际中双向链表使用较多,但单链表更适合作为面试题考察。

单链表这样一个相对“简陋”的数据结构,实际上就是为了考察面试者指针操作的基本功。很多题目需要修改指针链接,如果操作不当,会造成链表结点的丢失,或者出现错误的回路。

我们早在 C/C++ 编程课上就学过链表数据结构。你一定对各种链表的变体印象深刻,单链表、双链表、循环链表……但是在面试中,请忘记你记得的各种花哨样式,只使用最简单的单链表操作。面试官很可能不希望看到你的各种“奇技淫巧”:

  • 加入哑结点(即额外的链表头结点)可以简化插入操作,但面试官通常会要求你不要创建额外的链表结点,哑结点显然也属于额外的结点。
  • 使用 C/C++ 的二级指针可以让删除结点的代码非常精简,但如果面试官对此不熟悉的话,会看得一头雾水。

那么,如何才能简洁明了地解决单链表问题呢?实际上很多链表题目都是类型化的,都可以归结为链表的遍历,以及在遍历中做插入和删除操作。我们可以使用链表遍历的框架来解题。

链表遍历的基本框架

单链表操作的本质难度在哪里?相比于双向链表,单链表缺少了指向前一个结点的指针,所以在删除结点时,还需要持有前一个结点的指针。这让遍历过程变得麻烦了许多。

比较容易想到的方法是将遍历的指针指向“前一个结点”,删除结点时使用 p.next = p.next.next。但这个方法会带来一些心智负担:

  • 每次要查看的结点是 p.next,也就是下一个结点,别扭
  • 循环终止条件不是 p == null 而是 p.next == null,容易出错
0d0d80ba0ef1d8724a35f7ca7e8d3fdc.png
不是很好的链表遍历方式,有一定心智负担

实际上,这就是单链表操作的复杂性所在。我们前面也否定了使用二级指针这样的高级技巧来简化操作的方法,那么,有没有更简单明了的遍历方式呢?答案是有的。这里隆重推荐我一直在使用的链表遍历框架

当删除链表结点时,既需要访问当前结点,也需要访问前一个结点。既然这样,我们不妨使用两个指针来遍历链表,curr 指针指向当前结点,prev 指针指向前一个结点。这样两个指针的语义明确,也让你写出的代码更易理解。

93a6c7d496efffa72ac5d3516b3db62f.png
更好的链表遍历框架,指针意义清晰易懂

用代码写出来,链表遍历的框架是这样的:

ListNode prev = null;
ListNode curr = head;
while (curr != null) {
// 进行操作,prev 表示前一个结点,curr 表示当前结点
if (prev == null) {
// curr 是头结点时的操作
} else {
// curr 不是头结点时的操作
}
prev = curr;
curr = curr.next;
}

在遍历的过程中,需要一直维护 prevcurr 的前一个结点。curr 是循环中的主指针,整个循环的起始和终止条件都是围绕 curr 进行的。prev 指针作为辅助指针,实际上就是记录 curr 的上一个值。

在每一轮遍历中,可以根据需要决定是否使用 prev 指针。注意 prev 可能为 null(此时 curr是头结点),在使用前需要先进行判断。

a3290d996d36018b0fddec19f4e850dd.png
使用两个指针让删除结点非常容易:待删除
bf935866233e6b5bf22b1fcb59934da5.png
使用两个指针让删除结点非常容易:已删除

下面,我们看一看如何用这个链表遍历的框架来解决本期的例题:反转链表。

本期例题:反转链表的解法

反转链表的题目会有一个隐藏的要求:不能创建新的链表结点,只是在原有结点上修改链表指针。这样的原地操作会比生成一个新的链表要难很多。

f9fa787aa51043b6d7e2046eddd770a5.png
反转链表的目标:链表结点不变,修改链表指针

Step 1 套用框架

这道题实际上就是一个典型的链表的遍历-处理的操作,于是我们套用使用上面所讲的链表遍历框架。要反转链表,实际上就是要反转所有相邻结点之间的指针。那么,整体的代码框架应该是:

ListNode prev = null;
ListNode curr = head;
while (curr != null) {
// 反转 prev 和 curr 之间的指针
prev = curr;
curr = curr.next;
}

可以看到,遍历的框架已经将整体的思路架构了出来,我们知道按照如此的方式一定能遍历到所有相邻的结点对,也知道遍历结束后循环一定能正常退出。接下来只需要关注每一步如何反转结点之间的指针即可。

Step 2 写好单步操作

单步操作是“反转 prevcurr 之间的指针”。这里涉及到指针指向的改变,需要小心指针丢失的问题。在思考的时候,要考虑到和前一步、后一步的链接。

假设现在遍历到了链表中部的某个结点。链表应该会分成两个部分:prev 指针之前的一半链表已经进行了反转;curr 之后的一半链表还是原先的顺序。这次循环将让 curr 的指针改为指向 prev,就将当前结点从后一半链表放进了前一半链表。

69eaf9a52c536a873774675bb3b338e5.png
循环开始时,prev 和 curr 分别指向链表的前半部分和后半部分
fb78b2f85f2efba04cd31653a9a31cab.png
将当前结点放入前一半链表
a212e19ea9f93f62a4dd58c406c52438.png
下一轮循环时,prev 和 curr 仍然分别指向链表的前半部分和后半部分

而头结点的特殊情况是,全部链表都还未进行反转,即前一半链表为空。显然 curr.next 应当置为 null。

e63ee9b38b0e93948914422c638b5e47.png
当前结点为头结点时,前一半链表为空
b839deb4fd91adc70bf06a263bc93f4a.png
将 curr.next 置空,当前结点成为前一半链表的唯一结点

将单步操作放入代码框架,我们就得到了一份初版的解题代码:

ListNode prev = null;
ListNode curr = head;
while (curr != null) {
if (prev == null) {
curr.next = null;
} else {
curr.next = prev;
}
prev = curr;
curr = curr.next;
}

Step 3 细节处理

上面的代码已经基本上比较完整了,但是还存在着明显的错误,那就是存在指针丢失的问题。

我们使用 curr.next = prev 来反转指针,但这会覆盖掉 curr.next 本来存储的值。丢掉这个指针之后,链表的后续结点就访问不到了!

1430c6943556ee35db15789c5ab66b08.png
直接赋值 curr.next 是错误的,我们会丢掉指向下一个结点的指针

要解决指针丢失的问题也很简单,使用一个临时指针保存 curr 的下一个结点即可。如下图所示:

2ab0f831f04acfabf9e61f8abf5a14e0.png
使用临时指针保存下一个结点,避免指针丢失问题

不过这样一来,我们遍历框架中更新指针的操作也要随之进行微调。框架本来就不是一成不变的,需要根据实际题目灵活调整。

根据以上两点的细节处理,我们修改得到完整版的代码:

ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode cnext = curr.next;
if (prev == null) {
curr.next = null;
} else {
curr.next = prev;
}
prev = curr;
curr = cnext;
}
return prev;
}

上述代码中,if 的两个分支实际上可以优化合并,这里为了清晰起见仍然保留分支。

总结

总结起来,我们解决这一类单链表问题时,遵循的步骤是:

  1. 判断问题是否为链表遍历-修改,套用链表遍历框架
  2. 思考单步操作,将代码加入遍历框架
  3. 检查指针丢失等细节

有很多更复杂的链表题目都以反转链表为基础。下面列出了 LeetCode 上几道相关的题目:

  • LeetCode 203 - Remove Linked List Elements[2] 是一道直白的链表删除题目
  • LeetCode 445 - Add Two Numbers II[3] 以反转链表为基础解题
  • LeetCode 92 - Reverse Linked List II[4] 反转部分链表

希望本文的讲解能让你在写链表类题目时更得心应手。

参考资料

[1]

LeetCode 206 - Reverse Linked List: https://leetcode.com/problems/reverse-linked-list/

[2]

LeetCode 203 - Remove Linked List Elements: https://leetcode.com/problems/remove-linked-list-elements/

[3]

LeetCode 445 - Add Two Numbers II: https://leetcode.com/problems/add-two-numbers-ii/

[4]

LeetCode 92 - Reverse Linked List II: https://leetcode.com/problems/reverse-linked-list-ii/

---

由 五分钟学算法 原班人马打造的公众号:图解面试算法,现已正式上线!接下来我们将会在该公众号上,为大家分享优质的算法解题思路,坚持每天一篇原创文章的输出,视频动画制作不易,感兴趣的小伙伴可以关注点赞一下哈!

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

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

相关文章

1650显卡学计算机,适合老电脑升级?GTX1650显卡开箱,性价比依然不高!

原标题:适合老电脑升级?GTX1650显卡开箱,性价比依然不高!新一代的NV显卡高端版本基本上全部和大家见面,例如RTX2080ti、RTX2070等等。而中端的RTX2060和GTX1660ti也陆续被玩家接受,剩下入门级的显卡英伟达取…

iphone怎样关闭副屏_iPhone手机关掉这3个设置,不仅省电,而且手机还不会卡

现在很多人无论是吃饭睡觉上厕所手机都会形影不离,然而手机玩久了电量就会刷刷的往下掉,不仅如此长时间下去手机还会出现卡顿的情况。如何才能令手机不卡顿并且最大限度上省电呢?为大家分享几个技巧,关掉这3个设置,手机…

武汉计算机985211大学有哪些,武汉985211大学有哪些 武汉985211大学有什么

想必大家都听过去武大看樱花这句话吧!湖北最出名的大学可能就是武汉大学了,但是武汉大学不仅仅以其优秀的师资闻名,也以校园里美丽的樱花闻名。想要了解武汉985211大学有哪些,就来看看这些文章吧!武汉的985大学有武汉大学和华中科技大学。211…

vmci.sys版本不正确_这样安装 Python 库才是最正确的哦

优质文章,第一时间送达!分享一篇之前的文章,里面的这个Python知识点,可能很多人都还不清楚。平常我都是直接执行 pip install安装的第三方库,很多教程也是这么介绍的,一直以来我都认为这是标准的、正确的安…

概率计算机在线,高斯正态分布(概率)计算公式与在线计算器_三贝计算网_23bei.com...

输入平均值(M)、标准偏差(S)、数值(X1)、(选填)数值(X2)等已知量,点击计算按钮,可快速求出概率P(XX1)、概率P(XX2)、概率P(X1到X2)等结果。正态分布(Normal distribution),也称“常态分布”,又名高斯分布(Gaussian distribution)&…

mybatis 连接池_应用框架之Mybatis数据源和连接池

本文将从以下几个方面介绍Mybatis的数据源和连接池:MyBatis数据源DataSource分类数据源DataSource的创建过程DataSource什么时候创建Connection对象MyBatis数据源DataSource分类MyBatis数据源实现是在以下四个包中:MyBatis把数据源DataSource分为三种&am…

python默认安装地址_python多版本下设置python3为默认的方法

python3设置为多版本为默认的方法 如何在双python下设置python3为默认 在C:\Program下举例 第一步安装好python2和python3后设置好环境变量 第二步去掉python2根目录下的python.exe文件,还有Scripts文件夹下的pip.exe文件 第二步复制python3根目录下的python3.exe文…

惠普图形计算机游戏,惠普发布Victus by HP 16 - 一款适合PC游戏新人的游戏笔记本...

除了更新了其Omen系列的笔记本电脑,惠普今天还宣布了一个新的游戏笔记本电脑品牌,名为Victus by HP 16。看起来,Victus是为那些刚进入PC游戏世界的人准备的,他们可能不一定想买一台顶级的游戏笔记本作为他们的第一次尝试。虽然这表…

php 获取某一年最后一天_vivo年货节最后一天!多款机型大促,错过让你后悔再等一年!...

即将进入春节回乡高峰期,然而在这个时候许多小伙伴都会先购入春节必备用品。除了一些日用品外,手机也许是新年最佳的换机时间。最近,国产手机品牌vivo就开启了年货节活动,不过今天是最后一天了。vivo旗下众多商品都拥有不小的优惠…

计算机操作记录怎么删除,win7系统如何清除电脑使用记录

‍‍电脑被使用过后或大或少都会留下使用痕迹,就比如浏览器的历史记录,通过这个就可以看出电脑的主人喜欢访问的网站。有位win7 64位旗舰版用户向小编反馈,电脑要暂时借给朋友一段时间,要怎么消除自己的使用记录呢?下面…

分区供水条件口诀_口诀记忆 | 消防给水和消火栓系统的“月检”(重点学习)...

今日分享在消防给水和消火栓系统当中,维护管理及保养的周期,是综合能力与案例分析考试的重点所在,每年考查的分数在12分左右(两科之和),因此关于周期性维护管理的知识点,需要我们掌握并牢记。今天,我们把最…

php websocket 是否在线_看完让你彻底理解WebSocket原理,附实战代码(包含前端和后端)...

作者:nnngu来源:https://www.cnblogs.com/nnngu/p/9347635.html1、前言最近有同学问我有没有做过在线咨询功能。同时,公司也刚好让我接手一个 IM 项目。所以今天抽时间记录一下最近学习的内容。本文主要剖析了 WebSocket 的原理,以…

asp.net mvc项目实例_降龙-第13章:MVC开发准备

从这一章开始我将逐步研发一个MVC框架并内嵌在​我的开源项目中。由于内容会很多,所以我尽量拆分成多个章节来介绍,以展示框架研发的过程和一些​细节。上面上传的代码是整套MVC框架设计的起点,这些都是我们在spring中常见的注解类&#xff0…

waf可以检测哪个端口的流量_锐速云:CC防御过程中,WAF的主要特点有哪些?

一部分网站和游戏,以及金融的企业网站负责人员对于流量攻击应该属于耳熟能详。对此问题一直也是他们最头疼的。因此在解决DDoS攻击和CC攻击防御的过程中,运用了WAF指纹识别架构去做相对应的权限策略,以此避免误封正常的用户访问请求。这里的W…

frame中src怎么设置成一个变量_自动格式化打印变量HMLog介绍

作者 | mao2020来源 | 掘金,点击阅读原文查看作者更多文章前言在我初学iOS的时候,经常需要NSLog打印用于调试,有时候还需要打印多个变量:NSLog("xxxx frame% tag%ld isHidden%d", NSStringFromCGRect(view.frame), view…

电大计算机组成原理ppt,四川电大计算机组成原理(0023)第二次形考作业(课程号:5110023).docx...

四川电大计算机组成原理(0023)第二次形考作业(课程号:5110023).docx 计算机组成原理(0023)第二次形考作业四川电大形成性测评系统 课程代码5110023 参考资料 、单项选择题(共 7 道试题,共 35 分。)1. 组成一个运算器需要多个部件,但下面所列_…

android系统里面的mic是哪个app_安利 | 那些错过会后悔一年的法语APP

如今手机使用的频率越来越高,感觉每一个人都是“低头族”。既然我们都离不开手机,那我们何不利用手机来学习法语呢?今天我就跟大家推荐一些超级好用的各类法语APP,每个APP会注明IOS系统和android系统是否兼容。提高dicte和单词拼写…

北斗通信运营商_国内首个“北斗+5G”应用方案发布 配套5G产品将于年底量产上市...

北京合众思壮科技股份有限公司研发的各类北斗导航应用终端 郭超凯 摄北京合众思壮科技股份有限公司研发的各类北斗导航应用终端 郭超凯 摄中新网郑州9月10日电 (郭超凯)中国卫星导航与位置服务第八届年会10日在郑州召开,北京合众思壮科技股份有限公司(以下简称合众思…

Redis-运维

转自 极客时间 Redis 亚风 原文视频:https://u.geekbang.org/lesson/535?article681062 Redis 同步 Redis主从数据同步,主从第⼀次同步是全量同步 replicaof 主机 端口 #当前这个机器做Master的备份master如何判断slave是不是第⼀次来同步数据: Repl…

python括号的区别_Python中类-带括号与不带括号的区别

类不带括号我们叫赋值,带括号我们叫实例化。 什么是赋值? a7 ba id(7) 140726814208448 id(a) 140726814208448 id(b) 140726814208448 从上面例子中我们可以看出变量a赋值7、b赋值a,它们的内存地址都是相同的。 它们是指向了同一个内存地址&…