题目

原文:

Given a circular linked list, implement an algorithm which returns node at the beginning of the loop.

DEFINITION

Circular linked list: A (corrupt) linked list in which a node’s next pointer points to an earlier node, so as to make a loop in the linked list.

EXAMPLE

Input: A –> B –> C –> D –> E –> C [the same C as earlier]

Output: C

译文:

给定一个循环链表,实现一个算法返回这个环的开始结点。

定义:

循环链表:链表中一个结点的指针指向先前已经出现的结点,导致链表中出现环。

例子:

输入:A –> B –> C –> D –> E –> C [结点C在之前已经出现过]

输出:结点C

解答

关于带环链表的题目,《编程之美》中也有讲过。方法很tricky,设置快慢指针, (快指针速度为2,慢指针速度为1)使它们沿着链表移动,如果这个链表中存在环, 那么快指针最终会追上慢指针而指向同一个结点。接下来的问题是,快指针追上慢指针后, 怎么找到这个环的开始结点? 现在我们还没有答案,那让我们先来分析一下,快指针会在哪里追上慢指针。

无图无真相,先上图:

设环的开始结点(图中的D)前有k个结点,环有n个结点(上图中n从D到K共8个结点)。 快指针fast和慢指针slow一开始都指向头结点head,它们移动k步可到环的开始结点。 假设慢指针走过m个结点后,快指针追上了它,这时快指针走过了2m个结点。 快指针比慢指针多走过的结点都在环里转圈了,是环中结点数n的整数倍,即:

  1. 2m - m = pn --> m = pn, p为正整数

如果头结点是第一个结点的话,那么相遇结点就是第m+1=pn+1个结点。减去环之前的k 个结点,得到从环开始结点到相遇结点的结点数pn+1-k。相遇结点需要再经过 n-(pn+1-k)+1=(1-p)n+k个结点,才能回到环的开始结点(图中结点D)。 如果让快指针在相遇结点继续走,不过这次把速度变成了慢指针一样, 那么它要走(1-p)n+k步到达环开始结点,让慢指针从头结点head开始走, 它要走k步到达环开始结点。最后,它们将在环开始结点处相遇。

这个是怎么得出来的呢?假设快指针走了(1-p)n+k个结点到达环的开始结点,这时, 慢指针也走了(1-p)n+k步,它离环的开始结点还有

  1. k - [ (-p)n + k ] = (p-)n (步)

而这正好是环中结点数的整数倍,所以当慢指针到达环的开始结点时, 快指针(此时它的速度也是1)刚好在环中转了(p-1)圈,然后和慢指针在环的开始结点处相遇。

代码如下:

  1. node* loopstart(node *head){
  2. if(head==NULL) return NULL;
  3. node *fast = head, *slow = head;
  4. while(fast->next!=NULL){
  5. fast = fast->next->next;
  6. slow = slow->next;
  7. if(fast==slow) break;
  8. }
  9. if(fast->next==NULL) return NULL;
  10. slow = head;
  11. while(fast!=slow){
  12. fast = fast->next;
  13. slow = slow->next;
  14. }
  15. return fast;
  16. }

这个思路确实很巧很tricky。但,还有没有别的方法呢?更直观更简单的方法。 既然这么问了,当然是有了。:p一个无环的链表,它每个结点的地址都是不一样的。 但如果有环,指针沿着链表移动,那这个指针最终会指向一个已经出现过的地址。 答案是不是已经呼之欲出了。嗯,没错,哈希表!

以地址为哈希表的键值,每出现一个地址,就将该键值对应的实值置为true。 那么当某个键值对应的实值已经为true时,说明这个地址之前已经出现过了, 直接返回它就OK了。

由于C++标准中没有哈希表的操作,我用map进行模拟。不过哈希表的插入和取值操作是O(1) 的时间。而map是由一个RB tree组织,为了维护这个RB tree,插入和取值都会花更多的时 间。

代码如下:

  1. map<node*, bool> hash;
  2. node* loopstart1(node *head){
  3. while(head){
  4. if(hash[head]) return head;
  5. else{
  6. hash[head] = true;
  7. head = head->next;
  8. }
  9. }
  10. return head;
  11. }

完整代码如下:

  1. #include <iostream>
  2. #include <map>
  3. using namespace std;
  4.  
  5. typedef struct node{
  6. int data;
  7. node *next;
  8. }node;
  9.  
  10. node* init(int a[], int n, int m){
  11. node *head, *p, *q;
  12. for(int i=; i<n; ++i){
  13. node *nd = new node();
  14. nd->data = a[i];
  15. if(i==m) q = nd;
  16. if(i==){
  17. head = p = nd;
  18. continue;
  19. }
  20. p->next = nd;
  21. p = nd;
  22. }
  23. p->next = q;
  24. return head;
  25. }
  26.  
  27. node* loopstart(node *head){
  28. if(head==NULL) return NULL;
  29. node *fast = head, *slow = head;
  30. while(fast->next!=NULL){
  31. fast = fast->next->next;
  32. slow = slow->next;
  33. if(fast==slow) break;
  34. }
  35. if(fast->next==NULL) return NULL;
  36. slow = head;
  37. while(fast!=slow){
  38. fast = fast->next;
  39. slow = slow->next;
  40. }
  41. return fast;
  42. }
  43.  
  44. map<node*, bool> hash;
  45. node* loopstart1(node *head){
  46. while(head){
  47. if(hash[head]) return head;
  48. else{
  49. hash[head] = true;
  50. head = head->next;
  51. }
  52. }
  53. return head;
  54. }
  55. int main(){
  56. int n = , m = ;// m<n
  57. int a[] = {
  58. , , , , , , , , ,
  59. };
  60. node *head = init(a, n, m);
  61. //node *p = loopstart(head);
  62. node *p = loopstart1(head);
  63. if(p)
  64. cout<<p->data<<endl;
  65. return ;
  66. }

Cracking the coding interview--Q2.5的更多相关文章

  1. Cracking the coding interview

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

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

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

  3. Cracking the Coding Interview(Trees and Graphs)

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

  4. 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 ...

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

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

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

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

  7. 《Cracking the Coding Interview》——第13章:C和C++——题目6

    2014-04-25 20:07 题目:为什么基类的析构函数必须声明为虚函数? 解法:不是必须,而是应该,这是种规范.对于基类中执行的一些动态资源分配,如果基类的析构函数不是虚函数,那么 派生类的析构 ...

  8. 《Cracking the Coding Interview》——第5章:位操作——题目7

    2014-03-19 06:27 题目:有一个数组里包含了0~n中除了某个整数m之外的所有整数,你要设法找出这个m.限制条件为每次你只能用O(1)的时间访问第i个元素的第j位二进制位. 解法:0~n的 ...

  9. 二刷Cracking the Coding Interview(CC150第五版)

    第18章---高度难题 1,-------另类加法.实现加法. 另类加法 参与人数:327时间限制:3秒空间限制:32768K 算法知识视频讲解 题目描述 请编写一个函数,将两个数字相加.不得使用+或 ...

  10. 《Cracking the Coding Interview 》之 二叉树的创建 与 遍历(非递归+递归version)

    #include <iostream> #include <cstdio> #include <vector> #include <stack> #de ...

随机推荐

  1. mySQL把秒转换成日期

    mysql> SELECT SEC_TO_TIME (3600); +--------------------+ | SEC_TO_TIME (3600) | +---------------- ...

  2. 《TCP/IP具体解释卷2:实现》笔记--选路请求和选路消息

    内核的各种协议并不直接使用前面提供的函数来訪问选路树,而是调用几个函数:rtalloc和rtallocl是完毕路由表查询的两个 函数:rtrequest函数用于加入和删除路由表项:另外大多数接口在接口 ...

  3. Spring Boot热部署(springloader)

    使用方式一 在pom.xml文件添加依赖包: <plugin> <groupId>org.springframework.boot</groupId> <ar ...

  4. 开发Yii2过滤器并通过behaviors()行为调用(转)

    在Yii2的几乎每个controller中,我们都会看到一个函数behaviors(),通常,我们用这个函数来配置控制器的权限,例如:public function behaviors()    {  ...

  5. 读书笔记5基于matplotlib画图

    一.导入需要的模块 import numpy as np import matplotlib.pyplot as plt import seaborn as sns import scipy.stat ...

  6. 深入mysql “ON DUPLICATE KEY UPDATE” 语法的分析

    本篇文章是对mysql “ON DUPLICATE KEY UPDATE”语法进行了详细的分析介绍,需要的朋友参考下. mysql “ON DUPLICATE KEY UPDATE” 语法 如果在IN ...

  7. ASP.NET Web API 中 特性路由(Attribute Routing) 的重名问题

    刚才忘了说了,在控制器名重名的情况下,特性路由是不生效的.不然的话就可以利用特性路由解决同名的问题了. 而且这种不生效是真的不生效,不会提示任何错误,重名或者什么的,直接会报告404,所以也是个坑.

  8. oracle 连接字符串备份

    Oracle XE 标准连接 Oracle XE(或者"Oracle Database 10g Express Edition")是一个简单免费发布的版本. 以下是语法格式: Dr ...

  9. 每天一点儿Java--ComboBox

    import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.border.*; impor ...

  10. mybatis 一二事(1) - 简单介绍

    mybatis呢是一个orm数据库框架,非常适合新人学,门槛相对较低 本人呢曾经是先做的hibernate,后接触的mybatis,接触mabatis前我比较抵触,为啥呢, 当时喜欢hibernate ...