Cracking the coding interview--Q2.5
题目
原文:
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的整数倍,即:
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步,它离环的开始结点还有
k - [ (-p)n + k ] = (p-)n (步)
而这正好是环中结点数的整数倍,所以当慢指针到达环的开始结点时, 快指针(此时它的速度也是1)刚好在环中转了(p-1)圈,然后和慢指针在环的开始结点处相遇。
代码如下:
node* loopstart(node *head){
if(head==NULL) return NULL;
node *fast = head, *slow = head;
while(fast->next!=NULL){
fast = fast->next->next;
slow = slow->next;
if(fast==slow) break;
}
if(fast->next==NULL) return NULL;
slow = head;
while(fast!=slow){
fast = fast->next;
slow = slow->next;
}
return fast;
}
这个思路确实很巧很tricky。但,还有没有别的方法呢?更直观更简单的方法。 既然这么问了,当然是有了。:p一个无环的链表,它每个结点的地址都是不一样的。 但如果有环,指针沿着链表移动,那这个指针最终会指向一个已经出现过的地址。 答案是不是已经呼之欲出了。嗯,没错,哈希表!
以地址为哈希表的键值,每出现一个地址,就将该键值对应的实值置为true。 那么当某个键值对应的实值已经为true时,说明这个地址之前已经出现过了, 直接返回它就OK了。
由于C++标准中没有哈希表的操作,我用map进行模拟。不过哈希表的插入和取值操作是O(1) 的时间。而map是由一个RB tree组织,为了维护这个RB tree,插入和取值都会花更多的时 间。
代码如下:
map<node*, bool> hash;
node* loopstart1(node *head){
while(head){
if(hash[head]) return head;
else{
hash[head] = true;
head = head->next;
}
}
return head;
}
完整代码如下:
#include <iostream>
#include <map>
using namespace std; typedef struct node{
int data;
node *next;
}node; node* init(int a[], int n, int m){
node *head, *p, *q;
for(int i=; i<n; ++i){
node *nd = new node();
nd->data = a[i];
if(i==m) q = nd;
if(i==){
head = p = nd;
continue;
}
p->next = nd;
p = nd;
}
p->next = q;
return head;
} node* loopstart(node *head){
if(head==NULL) return NULL;
node *fast = head, *slow = head;
while(fast->next!=NULL){
fast = fast->next->next;
slow = slow->next;
if(fast==slow) break;
}
if(fast->next==NULL) return NULL;
slow = head;
while(fast!=slow){
fast = fast->next;
slow = slow->next;
}
return fast;
} map<node*, bool> hash;
node* loopstart1(node *head){
while(head){
if(hash[head]) return head;
else{
hash[head] = true;
head = head->next;
}
}
return head;
}
int main(){
int n = , m = ;// m<n
int a[] = {
, , , , , , , , ,
};
node *head = init(a, n, m);
//node *p = loopstart(head);
node *p = loopstart1(head);
if(p)
cout<<p->data<<endl;
return ;
}
Cracking the coding interview--Q2.5的更多相关文章
- Cracking the coding interview
写在开头 最近忙于论文的开题等工作,还有阿里的实习笔试,被虐的还行,说还行是因为自己的水平或者说是自己准备的还没有达到他们所需要人才的水平,所以就想找一本面试的书<Cracking the co ...
- Cracking the coding interview 第一章问题及解答
Cracking the coding interview 第一章问题及解答 不管是不是要挪地方,面试题具有很好的联系代码总用,参加新工作的半年里,做的大多是探索性的工作,反而代码写得少了,不高兴,最 ...
- Cracking the Coding Interview(Trees and Graphs)
Cracking the Coding Interview(Trees and Graphs) 树和图的训练平时相对很少,还是要加强训练一些树和图的基础算法.自己对树节点的设计应该不是很合理,多多少少 ...
- 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 ...
- 《Cracking the Coding Interview》读书笔记
<Cracking the Coding Interview>是适合硅谷技术面试的一本面试指南,因为题目分类清晰,风格比较靠谱,所以广受推崇. 以下是我的读书笔记,基本都是每章的课后习题解 ...
- Cracking the coding interview目录及资料收集
前言 <Cracking the coding interview>是一本被许多人极力推荐的程序员面试书籍, 详情可见:http://www.careercup.com/book. 第六版 ...
- 《Cracking the Coding Interview》——第13章:C和C++——题目6
2014-04-25 20:07 题目:为什么基类的析构函数必须声明为虚函数? 解法:不是必须,而是应该,这是种规范.对于基类中执行的一些动态资源分配,如果基类的析构函数不是虚函数,那么 派生类的析构 ...
- 《Cracking the Coding Interview》——第5章:位操作——题目7
2014-03-19 06:27 题目:有一个数组里包含了0~n中除了某个整数m之外的所有整数,你要设法找出这个m.限制条件为每次你只能用O(1)的时间访问第i个元素的第j位二进制位. 解法:0~n的 ...
- 二刷Cracking the Coding Interview(CC150第五版)
第18章---高度难题 1,-------另类加法.实现加法. 另类加法 参与人数:327时间限制:3秒空间限制:32768K 算法知识视频讲解 题目描述 请编写一个函数,将两个数字相加.不得使用+或 ...
- 《Cracking the Coding Interview 》之 二叉树的创建 与 遍历(非递归+递归version)
#include <iostream> #include <cstdio> #include <vector> #include <stack> #de ...
随机推荐
- JS取date的前一天时间
在javascript中取date的前一天时间: new Date(new Date()-24*60*60*1000),//取前一天的时间
- C 应用
前言 1)操作符两端必须加空格,(每行第一个赋值语句对齐). 2)变量名必须是英文(不能是拼音):英文.数字.下划线和美元符号. 3)等于号 == 反过来写(0 == i%4)防止少些赋值号的错误. ...
- 【js】indexOf()
/** **位置方法indexOf()和lastIndexOf() **这两个方法都接收两个参数:要查找的项和(可选的)表示查找起点位置的索引 **indexOf()方法从数组的开头(位置0)开始向后 ...
- MVC公司架构介绍-事件机制
- Linux卸载NAS磁盘,报device is busy
# umount /data umount.nfs: /data: device is busy umount.nfs: /data: device is busy # fuser -m -v /da ...
- C#--索引
索引是一组get和set访问器,类似于属性的访问器. 索引和属性在很多方面是相似的. 和属性一样,索引不用分配内存来存储: 索引和属性都主要被用来访问其他数据成员,这些成员和他们关联,他们为这些成员提 ...
- JavaScript - 返回头部
制作浮动top $(window).scroll( function() { var scrollValue=$(window).scrollTop(); scrollValue > 600 ? ...
- Spring cloud子项目
目前来说spring主要集中于spring boot(用于开发微服务)和spring cloud相关框架的开发,我们从几张图着手理解,然后再具体介绍: spring cloud子项目包括: Sprin ...
- ASP.NET的一些小问题
如何获取网站当前绝对路径? string path = HttpRuntime.AppDomainAppVirtualPath; 注:该路径结尾不含'/'.
- 基于FPGA的线阵CCD实时图像采集系统
基于FPGA的线阵CCD实时图像采集系统 2015年微型机与应用第13期 作者:章金敏,张 菁,陈梦苇2016/2/8 20:52:00 关键词: 实时采集 电荷耦合器件 现场可编程逻辑器件 信号处理 ...