链表非常重要!虽然理论内容不多,但链表上的操作却很复杂。所以,面试中经常会考察,你一定要掌握。
笔者最近刷了十几道链表相关的题,总结了一些技巧分享给大家。
掌握的程度
经典的链表题目包括:比如链表反转、求中间结点等,都需要轻松无 bug 地实现出来。
链表这一块真的是靠刷题,笔者之前也是对链表的知识一窍不通,理解也不是很深刻,通过刷题,现在基本上能够写出常见的链表操作题了~
以下分享几个解决链表题的技巧
技巧一:理解指针或引用的含义
链表的主要难点在于指针的操作,需要深刻理解指针的含义
将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量。
技巧二:警惕指针丢失和内存泄漏
插入结点时,一定要注意操作的顺序
不知道你有没有这样的感觉,写链表代码的时候,指针指来指去,一会儿就不知道指到哪里了。所以,我们在写的时候,一定注意不要弄丢了指针。
举个例子:要在 a 和 b 节点之间加一个 x 节点:
是下面这样写吗?答案是 NO!!!下面这样写是错误的,但是它非常符合我们的直觉,这也是容易出错的原因。那么到底哪出错了呢?
p->next = x; // 将p的next指针指向x结点;
x->next = p->next; // 将x的结点的next指针指向b结点;
p->next 指针在完成第一步操作之后,已经不再指向结点 b 了,而是指向结点 x。第 2 行代码相当于将 x 赋值给 x->next,自己指向自己。因此,整个链表也就断成了两半,从结点 b 往后的所有结点都无法访问到了。
举个例子:
好比排队,如果每个人只知道自己后面是谁。此时,若在你后面加一个新人,这时,你憨憨的跑去告诉这位新人,说:你排在我后面就对了,那么这位新人一定非常懵逼,心想 “我后面排的是谁呢?”
真正的做法应该是,你应该告诉这位新人:“你现在排在我原本后面那个人的前面,然后你排在我前面”
又好比职场晋升,你是不是应该先把手头上的事交给另外一个人,然后再让这个人跟在你后面办事
所以正确的应该是(上面的代码交互一下顺序)
x->next = p->next; // 将x的结点的next指针指向b结点;
p->next = x; // 将p的next指针指向x结点;
也许我说了这么多,你应该还是不理解其中的含义,那么就多刷题吧~
所以链表感觉像是:
技巧三:利用哨兵简化实现难度
首先,我们先来回顾一下单链表的插入和删除操作。如果我们在结点 p 后面插入一个新的结点(new_node),只需要下面两行代码就可以搞定。
new_node->next = p->next;
p->next = new_node;
但是,当我们要向一个空链表中插入第一个结点,刚刚的逻辑就不能用了。我们需要进行下面这样的特殊处理,其中 head 表示链表的头结点。所以,从这段代码,我们可以发现,对于单链表的插入操作,第一个结点和其他结点的插入逻辑是不一样的。
if (head == null) {
head = new_node;
}
我们再来看单链表结点删除操作。如果要删除结点 p 的后继结点,我们只需要一行代码就可以搞定。
p->next = p->next->next;
但是,如果我们要删除链表中的最后一个结点,前面的删除代码就不 work 了。跟插入类似,我们也需要对于这种情况特殊处理。写成代码是这样子的:
if (head->next == null) {
head = null;
}
针对链表的插入、删除操作,需要对插入第一个结点和删除最后一个结点的情况进行特殊处理。
这样代码实现起来就会很繁琐,不简洁,而且也容易因为考虑不全而出错。如何来解决这个问题呢?
还记得如何表示一个空链表吗?head=null 表示链表中没有结点了。其中 head 表示头结点指针,指向链表中的第一个结点。
如果我们引入哨兵结点,在任何时候,不管链表是不是空,head 指针都会一直指向这个哨兵结点。我们也把这种有哨兵结点的链表叫带头链表。相反,没有哨兵结点的链表就叫作不带头链表。
下面图中展示的是一个带头链表,你可以发现,哨兵结点是不存储数据的。因为哨兵结点一直存在,所以插入第一个结点和插入其他结点,删除最后一个结点和删除其他结点,都可以统一为相同的代码实现逻辑了。
如果你不知道什么时候要使用哨兵节点,那就无脑使用吧~~
技巧四:重点留意边界条件处理
经常用来检查链表代码是否正确的边界条件有这样几个:
- 如果链表为空时,代码是否能正常工作?
- 如果链表只包含一个结点时,代码是否能正常工作?
- 如果链表只包含两个结点时,代码是否能正常工作?
- 代码逻辑在处理头结点和尾结点的时候,是否能正常工作?
同时可能需要留意的点还有
- 如果要切断一个链表,那么需要把前半部分链表的最后一个节点的 next 指针指向空
技巧五:举例画图,辅助思考
画图帮助理解,能够让你快速的写出 Bug Free 的代码,也能搞清楚其中的逻辑
解决链表问题的一个比较常用的思想是:快慢指针
快慢指针在解决环形链表、找链表第 K 个位置的值时非常有用,需要认真掌握
技巧六:多写多练,没有捷径
以下是我刷过的有关链表的题目
书山有路勤为径,熟能生巧是良计!!!
刷题记录分享
笔者将自己的数据结构与算法刷题过程记录在语雀的一个仓库中,有兴趣的看一下
数据结构与算法学习之路 · 语雀www.yuque.comhttp://weixin.qq.com/r/qUNvd9TEU4ksrZAU9xa7 (二维码自动识别)