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

本期例题: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也陆续被玩家接受,剩下入门级的显卡英伟达取…

python参数类型_Python 参数类型和参数匹配模型

Python 方法的参数种类有很多,而不是通常语言定义的那样, Python 方法的传参能力要比想象的强大很多。很多初学者可能对一些库中带 * 带 ** 的参数类型非常奇怪,但是其实这些语法正是保证 Python 方法传参强大的重要因素。 First Thing 首先要…

city如何使用 mega unity_制作思路和CityEngine的简单使用

最近什么都不想干,就总结一下之前做过的一些东西。首先是建立一个虚拟城市,目前这个城市还在建造中,主要是用于自己的之后的动画和开放世界游戏制作,下面说以下自己之前的思路。说起城市建模,可以说是哗的一下一大堆可…

计算机网络结构有哪些,计算机网络主要拓扑结构有哪些

计算机网络的最主要的拓扑结构有总线型拓扑、环形拓扑、树形拓扑、星形拓扑、混合型拓扑以及网状拓扑。其中环形拓扑、星形拓扑、总线型拓扑是三个最基本的拓扑结构。在局域网中,使用最多的是星形结构。网络的拓扑结构:网络拓扑结构是指抛开网络电缆的物…

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

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

wxpython按钮形状如何修改_Python图形化界面入门教程 - 使用wxPython自定义表

原标题:Python图形化界面入门教程 - 使用wxPython自定义表 来自: Linux迷 网址:https://www.linuxmi.com/python-gui-wxpython-zidingyi-biao.html 在此Python GUI中,我们学习下在wxPython中显示“定制表” 。在第一部分中&#x…

武汉大学计算机考研 专业课程,2018武汉大学计算机考研复试经验贴

2018武汉大学计算机考研复试经验贴武汉大学发布于2019年9月22日 12:25阅读数 18196初试唯一要讲的就是专业课问题,今年专业课改革,只考两门专业课。一门是数据结构,分值为90分,只有选择题和代码题,大概24个选择题&…

hi3798mv300是什么手机_华为海思电视芯片hi3798mv300 硬件参数如何?

这个是MV300的参数http://www.hisilicon.com.cn/-/media/Hisilicon/pdf/STB/Hi3798MV300.pdf​www.hisilicon.com.cn这个是MV200的参数http://www.hisilicon.com/-/media/Hisilicon/pdf/STB/Hi3798MV200.pdf​www.hisilicon.com两者对比:从参数来看,V300…

python 字符串分割_python中分割字符串split切割并选择输出 逐行读取文件后字符串拼接...

字符串根据分隔符切割后输出,一下面 #!/usr/bin/python # -*- coding: utf-8 -*- str"abc,123 efg,567" #以空行分割后输出 print str.split(); #以,分割2次后输出 print str.split(,,2); #以,分割2次后输出地2个参数,1是从0开始,代…

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

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

迪信通机器人_迪信通要做机器人 玩票还是另有深意?

原标题:迪信通要做机器人 玩票还是另有深意?“迪信通要玩场大的。”迪信通(06188.HK)总裁金鑫近日向第一财经记者表示。金鑫所说,指向迪信通正在布局的一系列转型。迪信通是国内手机线下连锁渠道商,2015年,在手机行业整体增速放缓…

defaultdict python_Python使用defaultdict读取文件各列的方法

本文实例讲述了Python使用defaultdict读取文件各列的方法。分享给大家供大家参考,具体如下: #!/usr/bin/python """USAGE: python *.py align_SNP_site out_file""" import sys #import time from collections import Cou…

为什么输入法显示中文打不出中文_搜狗输入法打不出中文怎么回事 打不出汉字解决办法...

搜狗输入法有的时候在电脑上打不出中文的情况你有遇到过吗?想知道如何修复搜狗输入法?不妨来看看本站提供的详细教程吧!搜狗输入法打不出中文怎么回事?第一种:我们可以在搜狗输入法的“设置属性”――输入法修复器――…

html一排小标题怎么写,工作总结大标题小标题怎么写

大凡公文中所说的标题主要有两种:一种是公文本身的标题。根据中办发《党政机关公文处理工作条例》中的表述,“标题由发文机关名称、事由和文种组成。”按照此义,这里的标题应是指公文的总标题,也有人称之为题目、名称、大标题或主…

github 公钥 私钥_ubuntu git生成ssh key (公钥私钥)配置github或者码云

Git是分布式的代码管理工具,远程的代码管理是基于SSH的,所以要使用远程的Git则需要SSH的配置。github的SSH配置如下:设置Git的user name和email:git config --global user.name "xx"git config --global user.email &qu…

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

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

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

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

linux搭建lnnp_linux主机安装lnmp详细步骤

首先登陆ssh登陆,然后安装lnmp:wget http://soft.vpser.net/lnmp/lnmp1.6.tar.gz -cO lnmp1.6.tar.gz && tar zxf lnmp1.6.tar.gz && cd lnmp1.6 && ./install.sh lnmp大概40-50分钟然后##添加网站(虚拟主机)##lnmp vhost add提…

python操作文件open_python:open/文件操作

open/文件操作 fopen(/tmp/hello,w) #open(路径文件名,读写模式) #读写模式:r只读,r读写,w新建(会覆盖原有文件),a追加,b二进制文件.常用模式 如:rb,wb,rb等等 读写模式的类型有: rU 或 Ua 以读方式打开, 同时提供通用换行符支持 (PEP 278) csv w 以写方式打开&…

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

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