前言
最近在“极客时间”学习王争老师《数据结构与算法之美》课程,本文用作学习过程中的代码实践记录。主要是针对一条带头单链表进行链表反转、查询中间结点,检查是否有环,删除倒数第几个结点和合并两个有序链表的操作。
代码
话不多说,直接上示例代码:
1 | package com.structure; |
代码整体来讲,多看几遍就懂了,其中比较难理解可能就是检测链表是否有环、找出中间结点和删除倒数第几个结点的操作,这三个方法都用到了快慢指针来做。
检测链表是否有环
首先介绍链表上有环是什么样的情况,请看下图:
我们可以看到链表在B处就进入了一个环,那么我们怎么判断一个链表中是否有环呢?
我们可以设置slow和fast两个指针都指向链表的头部,然后呢slow每次走一步,fast每次走两步,如果fast能到链表尾部那当然就没有环,而如果fast不能到链表尾部,那么它就会在环上一只跑,最终fast会与后来的slow在环上相遇,此时fast == slow,则证明有环。
1 | // 检测链表中是否有环 |
另外一个问题,我们怎么知道这个环的入口结点是哪个呢?
如上图所示,假设链表头A到入口点B的长度为x,C点为slow和fast相遇的地方,环的长度为L,slow走过的距离为S并且当前slow已经在环上走了m圈,fast要与slow相遇它一定比slow多走了n圈,并且fast走的距离是slow的两倍2S。B到C的长度为z,C到B的长度为y,那么我们可以推导出如下公式:
X + Z + mL = S
X + Z + mL + nL = 2S
Z + Y = L
那么
X + Z + mL + nL = 2(X + Z + mL) => nL = X + Z + mL =>
nL = X + (L - Y) + mL => X = (n - m - 1)L + Y
最终得到X = (n - m - 1)L + Y
所以如果我们设置两个指针分别指向链表头A点和C点,两个指针每次走一步,最终会经过(n - m - 1)圈后会相遇,第一次相遇的地方就是环的入口点。
1 | // 找出环的入口 |
找出中间结点
1 | // 找出中间的结点 |
这个就比较简单了,就是利用快慢节点分别从链表头,fast每次走两步,slow每次走一步,那么当fast走到链表尾的时候,slow刚好走到中间的位置上,此时slow就是中间结点。
删除倒数第几个结点
1 | // 删除倒数第K个结点 |
这个也比较好理解,就是比如我们要删除倒数第二个点,链表长度为L,那么我们让fast先走两步,然后再让fast和slow一起每次走一步,假设fast总共走了n步到了链表尾,此时slow走了n - 2步,就处在倒数第二个位置上。