给定一个有环链表,实现一个算法返回环路的开头节点。 这个问题是由经典面试题-检测链表是否存在环路演变而来。这个问题也是编程之美的判断两个链表是否相交的扩展问题。

首先回顾一下编程之美的问题。 由于如果两个链表如果相交,那么交点之后node都是共享(地址相同)的,因此最简单暴力的方法就是两个for循环,判断该链表的node是否属于另外一个链表。但是这个算法复杂度是O(length1 * length2)。如果链表较长,这个复杂度有点高了。

当然也可以遍历其中某个链表,将node的地址存储hash table中;然后接下来遍历另外一个链表,查找node是否在这个hash table中。这样的话时间复杂度就是O(length1 + length2)。但是需要额外的O(length1)的空间。在C++ STL Java等主流语言中,hash table都有很好的实现,因此编程也比较简单。

注意我们这个方法是认定如果两个链表相交,那么肯定交点以后的点都是共享的,包括最后一个点。那么找到链表的最后一个节点,如果节点相同,那么就相交。当然了,以上算法需要处理有环存在的情况。

本文的问题是寻找有环链表的开头节点,那么如何判断一个链表是否有环?

我们可以用快慢游标的方法。具体来说,就是使用两个游标遍历链表,其中快游标(快指针,fastRunner)每次经过两个node,慢游标(慢指针,slowRunner)每次经过一个node。那么如果有环,那么这两个游标肯定会相遇。使用反证法可以推理这个推论是正确的,假如fastRunner正好经过了slowRunner,而没有相遇,那么上一部,就是fastRunner后退两步,slowRunner后退一步,两个runner必定相遇。如果现在slowRunner正好领先一步fastRunner,那么下一步二者相遇。

bool checkLinkRing(const Node *head)
{
if( head == nullptr )
return false; auto fastRunner = head;
auto slowRunner = head;
while( fastRunner->next != nullptr && fastRunner->next->next != nullptr )
{
fastRunner = fastRunner->next->next;
slowRunner = slowRunner->next;
if( fastRunner == slowRunner )
return true;
} return false;
}

接下来的问题,如何找到环的起始点?看下图,假设交点在K处

那么在slowRunner走了K步到达交点,那么此时fastRunner走了2K步到达黄圈处。那么fastRunner距离追上slowRunner还有RingSize - K步。 注意,只能顺时针前进。fastRunner相对slowRunner的速度是每步经过一个node(fastRunner虽然每次经过2个node,但是slowRunner也向前走了一个node,因此每步二者的距离仅仅减少一个node),因此在走了RingSize - K时相遇。也就是slowRunner在进入环后,再走RingSize
- K 步后二者相遇于绿处。此时,绿点顺时针距离交点有RingSize - ( RingSize - K) = K。注意括号内的RingSize - K是slowRunner走的。

关键问题出来了,在二者相遇的点,距离交点也是K: 与head到交点的距离相同。因此,我们可以调整两个游标,使slowRunnder从头开始,fastRunnder从相遇点开始,二者以相同的速度前行,必然在相交点再次相遇!

auto findRingCrossPoint(const Node *head) ->decltype(head)
{
if( head == nullptr )
return nullptr; auto fastRunner = head;// C++11, fastRunner is const Node *
auto slowRunner = head;
while( fastRunner->next != nullptr && fastRunner->next->next != nullptr )
{
fastRunner = fastRunner->next->next;
slowRunner = slowRunner->next;
if( fastRunner == slowRunner )
break;
} // in case that there is no ring.
if( fastRunner != slowRunner )
return nullptr; slowRunner = head;
while( true )
{
fastRunner = fastRunner->next;
slowRunner = slowRunner->next;
if( fastRunner == slowRunner )
{
break;
}
} return fastRunner;
}

Cracking the Coding Interview:: 寻找有环链表的环路起始节点的更多相关文章

  1. Cracking The Coding Interview 2.0 单链表

    #include <iostream> #include <string> using namespace std; class linklist { private: cla ...

  2. Cracking the Coding Interview(Trees and Graphs)

    Cracking the Coding Interview(Trees and Graphs) 树和图的训练平时相对很少,还是要加强训练一些树和图的基础算法.自己对树节点的设计应该不是很合理,多多少少 ...

  3. Cracking the Coding Interview(Stacks and Queues)

    Cracking the Coding Interview(Stacks and Queues) 1.Describe how you could use a single array to impl ...

  4. 《Cracking the Coding Interview》读书笔记

    <Cracking the Coding Interview>是适合硅谷技术面试的一本面试指南,因为题目分类清晰,风格比较靠谱,所以广受推崇. 以下是我的读书笔记,基本都是每章的课后习题解 ...

  5. Cracking the coding interview

    写在开头 最近忙于论文的开题等工作,还有阿里的实习笔试,被虐的还行,说还行是因为自己的水平或者说是自己准备的还没有达到他们所需要人才的水平,所以就想找一本面试的书<Cracking the co ...

  6. Cracking the coding interview 第一章问题及解答

    Cracking the coding interview 第一章问题及解答 不管是不是要挪地方,面试题具有很好的联系代码总用,参加新工作的半年里,做的大多是探索性的工作,反而代码写得少了,不高兴,最 ...

  7. Cracking the coding interview目录及资料收集

    前言 <Cracking the coding interview>是一本被许多人极力推荐的程序员面试书籍, 详情可见:http://www.careercup.com/book. 第六版 ...

  8. Cracking the Coding Interview 150题(一)

    1.数组与字符串 1.1 实现一个算法,确定一个字符串的所有字符是否全都不同.假设不允许使用额外的数据结构,又该如何处理? 1.2 用C或C++实现void reverse(char* str)函数, ...

  9. 《Cracking the Coding Interview》——第2章:链表——题目6

    2014-03-18 02:41 题目:给定一个带有环的单链表,找出环的入口节点. 解法1:用hash来检测重复节点肯定是容易想而且效率也高的好办法. 代码: // 2.6 You have a ci ...

随机推荐

  1. Jupyter notebook 输出含中文的pdf 方法

    我电脑 OS 是 Ubuntu14.04, 可用的最简单方法是: 打开终端,输入 sudo find / -name article.tplx 用以查找 article.tplx 文件位置,我电脑的结 ...

  2. E1

    en表"使怎么样" engage 吸引,从事,订婚    be engaged in doing sth.  忙于 endure  忍耐,忍受 enforce 强制执行 enrol ...

  3. Node.js 教程

    简单的说 Node.js 就是运行在服务端的 JavaScript. Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台. Node.js是一个事件驱动I/O服务端Ja ...

  4. 如何处理JS,css与smarty标签的冲突

    smarty的默认标记符是大括号:{}, 假如我们页面上有JS且定义了函数或者对象,或者有CSS定义了样式,就会出现大括号, smary引擎就会把这些大括号当作smarty语法来解释, 很明显,这些C ...

  5. WebRTC 音频算法 附完整C代码

    WebRTC提供一套音频处理引擎, 包含以下算法: AGC自动增益控制(Automatic Gain Control) ANS噪音抑制(Automatic Noise Suppression) AEC ...

  6. PTA中如何出Java题目?

    PTA中如何出Java题目? 很多第一次出题的老师,不知道Java在PTA中是如何处理输入的.写一篇文章供大家参考.比如以下这样的一个题目: 从控制台读入两个数,然后将其相加输出. 对于该题可以有如下 ...

  7. linux系统性能监控--网络利用率

    Linux中提供了许多有助于评估各种 Linux网络性能的监视工具,其中一些监视工具也可用于解决网络问题以及监视性能. Linux内核为用户提供了大量的网络系统信息,这有助于监视网络的健康状态并检测在 ...

  8. ROS新功能包PlotJuggler绘图

    http://www.ros.org/news/2017/01/new-package-plotjuggler.html PlotJuggler,一个基于Qt的应用程序,允许用户加载,搜索和绘图数据. ...

  9. OpenResty 自定义 access_log 格式

    定义access log的format是 Nginx已经提供的功能,有了 ngx_lua 之后就可以更灵活的记录请求相关的信息,而不仅仅拘泥于 Nginx的内置变量了,可以自定义一些格式和变量来存储结 ...

  10. gdb不知为何显示2次析构

    gdb不知为何显示2次析构 (金庆的专栏 2016.11) gdb 显示2次 A::~A(): (gdb) bt #0 A::~A (this=0x602010, __in_chrg=<opti ...