掌握的程度

链表非常重要!虽然理论内容不多,但链表上的操作却很复杂。所以,面试中经常会考察,你一定要掌握。

经典的链表题目包括:比如链表反转、求中间结点等,都需要轻松无 bug 地实现出来。

链表这一块真的是靠刷题,笔者之前也是对链表的知识一窍不通,理解也不是很深刻,通过刷题,现在基本上能够写出常见的链表操作题了~

以下分享几个解决链表题的技巧

技巧一:理解指针或引用的含义

链表的主要难点在于指针的操作,需要深刻理解指针的含义

将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量。

技巧二:警惕指针丢失和内存泄漏

插入结点时,一定要注意操作的顺序

不知道你有没有这样的感觉,写链表代码的时候,指针指来指去,一会儿就不知道指到哪里了。所以,我们在写的时候,一定注意不要弄丢了指针。
举个例子:要在 a 和 b 节点之间加一个 x 节点:
image.png
是下面这样写吗?答案是 NO!!!下面这样写是错误的,但是它非常符合我们的直觉,这也是容易出错的原因。那么到底哪出错了呢?

  1. p->next = x; // pnext指针指向x结点;
  2. x->next = p->next; // x的结点的next指针指向b结点;

p->next 指针在完成第一步操作之后,已经不再指向结点 b 了,而是指向结点 x。第 2 行代码相当于将 x 赋值给 x->next,自己指向自己。因此,整个链表也就断成了两半,从结点 b 往后的所有结点都无法访问到了。

举个例子:

好比排队,如果每个人只知道自己后面是谁。此时,若在你后面加一个新人,这时,你憨憨的跑去告诉这位新人,说:你排在我后面就对了,那么这位新人一定非常懵逼,心想“我后面排的是谁呢?”

真正的做法应该是,你应该告诉这位新人:“你现在排在我原本后面那个人的前面,然后你排在我前面”


> 又好比职场晋升,你是不是应该先把你屁股后面的事交给另外一个人,然后再让这个人跟在你屁股后面

所以正确的应该是(上面的代码交互一下顺序)

  1. x->next = p->next; // x的结点的next指针指向b结点;
  2. p->next = x; // pnext指针指向x结点;

也许我说了这么多,你应该还是不理解其中的含义,那么就多刷题吧~

所以链表感觉像是:
unnamed.png

技巧三:利用哨兵简化实现难度

首先,我们先来回顾一下单链表的插入和删除操作。如果我们在结点 p 后面插入一个新的结点(new_node),只需要下面两行代码就可以搞定。

  1. new_node->next = p->next;
  2. 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 指针都会一直指向这个哨兵结点。我们也把这种有哨兵结点的链表叫带头链表。相反,没有哨兵结点的链表就叫作不带头链表。

下面图中展示的是一个带头链表,你可以发现,哨兵结点是不存储数据的。因为哨兵结点一直存在,所以插入第一个结点和插入其他结点,删除最后一个结点和删除其他结点,都可以统一为相同的代码实现逻辑了。

【链表】链表代码技巧、需要掌握的程度、题目 - 图3

如果你不知道什么时候要使用哨兵节点,那就无脑使用吧~~

技巧四:重点留意边界条件处理

经常用来检查链表代码是否正确的边界条件有这样几个:

  • 如果链表为空时,代码是否能正常工作?
  • 如果链表只包含一个结点时,代码是否能正常工作?
  • 如果链表只包含两个结点时,代码是否能正常工作?
  • 代码逻辑在处理头结点和尾结点的时候,是否能正常工作?

同时可能需要留意的点还有

  • 如果要切断一个链表,那么需要把前半部分链表的最后一个节点的 next 指针指向空


技巧五:举例画图,辅助思考

画图帮助理解,能够让你快速的写出 Bug Free 的代码,也能搞清楚其中的逻辑

【链表】链表代码技巧、需要掌握的程度、题目 - 图4

解决链表问题的一个比较常用的思想是:快慢指针

快慢指针在解决环形链表、找链表第 K 个位置的值时非常有用,需要认真掌握

技巧六:多写多练,没有捷径

以下是我刷过的有关链表的题目

书山有路勤为径,熟能生巧是良计!!!
image.png

5 个常见的链表操作(后面的数字代表在 LeetCode 的位置)

  • 实现单链表、循环链表、双向链表,支持增删操作
  • 单链表反转 206

语雀内容

  • 链表中环的检测 141

语雀内容

  • 两个有序的链表合并 21

语雀内容

  • 删除链表倒数第 n 个结点 19
  • 求链表的中间结点 876

语雀内容