Cracking the Coding Interview:: 寻找有环链表的环路起始节点

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

首先回顾一下编程之美的问题。 由于如果两个链表如果相交,那么交点之后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;
}
时间: 2024-09-22 14:31:47

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

Cracking the coding interview

CareerCup 目录 Chapter 1 | Arrays and Strings 1.1  Implement an algorithm to determine if a string has all unique characters. What if you can not use additional data structures? 1.2 Write code to reverse a C-Style String. (C-String means that "abcd&quo

单链表-寻找一个单向链表的中项 java 调用问题!谢谢大家

问题描述 寻找一个单向链表的中项 java 调用问题!谢谢大家 //寻找一个单向链表的中项,如果存在两个则返回前一个,给出算法描述,并实现 package pra_bd; import java.awt.DisplayMode; import javax.naming.spi.DirStateFactory.Result; import org.w3c.dom.Node; public class LinkList { //头结点 Link first; //单链表构造函数 public Lin

指针-寻找一个单向链表的中项,如果存在两个则返回前一个

问题描述 寻找一个单向链表的中项,如果存在两个则返回前一个 import javax.naming.spi.DirStateFactory.Result; public class LinkList { //头结点 Link first; //单链表构造函数 public LinkList(){ first = null; } //判断单链表是否为空 public boolean isEmpty(){ return (null==first); } //在单链表头插入新结点 public voi

[华为机试练习题]24.删除链表中的重复节点、剩余节点逆序输出

题目 描述: 题目描述: 输入一个不带头节点的单向链表(链表的节点数小于100),删除链表中内容重复的节点(重复的节点全部删除),剩余的节点逆序倒排. 要求实现函数: void vChanProcess(strNode * pstrIn,strNode * pstrOut); [输入] pstrIn:输入一个不带头节点的单向链表 [输出] pstrOut:删除内容重复的节点(重复的节点全部删除),剩余节点逆序输出(不带头节点,链表第一个节点的内存已经申请). [注意]只需要完成该函数功能算法,中

[华为机试练习题]49.向升序单向链表中插入一个节点

题目 描述: 输入一个升序单向链表和一个链表节点,向单向链表中按升序插入这个节点. 输入为空指针的情况视为异常,另外不考虑节点值相等的情况. 链表结点定义如下: struct ListNode { int m_nKey; ListNode* m_pNext; }; 详细描述: 接口说明 原型: ListNode* InsertNodeToList(ListNode* pListHead, ListNode* pInsertNode); 输入参数: ListNode* pListHead 单向链表

mfc ++-CObList 链表加不进去节点

问题描述 CObList 链表加不进去节点 A.B两个类都继承了COblist A类里定义一个COblist 链表,要存放B类的数据. 往这个链表里加节点加不进去,是链表没初始化吗(需要初始化吗?),还是其他原因? 解决方案 什么叫做"加节点加不进去"?没初始化调用会崩溃的吧. 还是上代码吧. 解决方案二: class zdatabase : public CObList class zdbconnect: public CObList CObList m_ListDatabase(在

数据结构Java实现04----循环链表、仿真链表

单向循环链表 双向循环链表 仿真链表   一.单向循环链表: 1.概念: 单向循环链表是单链表的另一种形式,其结构特点是链表中最后一个结点的指针不再是结束标记,而是指向整个链表的第一个结点,从而使单链表形成一个环. 和单链表相比,循环单链表的长处是从链尾到链头比较方便.当要处理的数据元素序列具有环型结构特点时,适合于采用循环单链表. 和单链表相同,循环单链表也有带头结点结构和不带头结点结构两种,带头结点的循环单链表实现插入和删除操作时,算法实现较为方便. 带头结点的循环单链表的操作实现方法和带头

剑指offer系列之五十九:链表中环的入口节点

题目描述 一个链表中包含环,请找出该链表的环的入口结点. 此题的思路其实 很简单,之所以出现环,是因为在整个链表中出现了重复的节点,而遇到的第一个重复的节点就是环的入口节点.所以可以使用Set来保存遍历到的节点,因为Set集合是不允许出现重复元素的,所以当一个节点被第二次添加的时候,往Set中放元素是失败的.所以可以利用这一点找出第一个重复的元素.基于这种思路的代码比较简洁,代码如下(已被牛客AC): import java.util.HashSet; import java.util.Set;

Google Interview University - 坚持完成这套学习手册,你就可以去 Google 面试了

本文讲的是Google Interview University - 坚持完成这套学习手册,你就可以去 Google 面试了, 这是我为了从 web 开发者(自学.非计算机科学学位)蜕变至 Google 软件工程师所制定的计划,其内容历时数月. 这一长列表是从 Google 的指导笔记 中萃取出来并进行扩展.因此,有些事情你必须去了解一下.我在列表的底部添加了一些额外项,用于解决面试中可能会出现的问题.这些额外项大部分是来自于 Steve Yegge 的"得到在 Google 工作的机会&quo