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

首先回顾一下编程之美的问题。 由于如果两个链表如果相交,那么交点之后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. 关于npm run build 报错解决方案

    # 特定的错误 ERROR in statics/mobile/js/vendor.cef13a0e680a5bc0d8b3.js from UglifyJsUnexpected token: pun ...

  2. ACM KMP 格式输入导致TLE

    在写 Oulipo   POJ - 3461 时候遇上的奇怪的问题 在格式输入上不一样,提交的时候返回TLE,两段代码如下: A#include<iostream> #include< ...

  3. Go 语言变量

    变量来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念.变量可以通过变量名访问. Go 语言变量名由字母.数字.下划线组成,其中首个字母不能为数字. 声明变量的一般形式是使用 var 关键字: ...

  4. PHP 实例 AJAX 与 MySQL

    AJAX 数据库实例 下面的实例将演示网页如何通过 AJAX 从数据库读取信息: 实例   Person info will be listed here... 实例解释 - MySQL 数据库 在上 ...

  5. Docker 联合文件系统

    联合文件系统(UnionFS)是一种分层.轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several dir ...

  6. JAVAEE——BOS物流项目11:在realm中授权、shiro的方法注解权限控制、shiro的标签权限控制、总结shiro的权限控制方式、权限管理

    1 学习计划 1.在realm中进行授权 2.使用shiro的方法注解方式权限控制 n 在spring文件中配置开启shiro注解支持 n 在Action方法上使用注解 3.★使用shiro的标签进行 ...

  7. 安卓高级5 zXing

    ZXing作者的github地址: https://github.com/zxing/zxing 这里为大家也提供一个封装好的最新的ZXing Lib: https://github.com/xuyi ...

  8. 5秒让你的View变3D,ThreeDLayout使用和实现

    在很久很久以前,写了一篇自定义3d view的博客.但是只是讲了如何实现,实现起来还是比较耗时,所以本着平易近人的心态,把他封装成了一个ViewGroup,只需要在你的view或者布局外面包裹一层Th ...

  9. Android的Intent机制详解

    Intent 是一个消息传递对象,您可以使用它从其他应用组件请求操作.尽管 Intent 可以通过多种方式促进组件之间的通信,但其 基本用例主要包括以下三个: 启动 Activity: Activit ...

  10. ECharts, PHP, MySQL, Ajax, JQuery 实现前后端数据可视化

    ECharts 下载js代码 工作原理浅析 在项目中引入ECharts 后台处理 数据库端MySQL PHP端 JQuery Ajax处理 ECharts 端处理 前端全部代码 演示结果 总结 最近要 ...