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 ...
随机推荐
- Android快速开发不可或缺的11个工具类(下载)
功能分类:工具 支持平台:Android 运行环境:Eclipse 开发语言:Java 开发工具:Eclipse 源码大小:11.45KB 下载地址:ht ...
- clscfg.bin: error while loading shared libraries: libcap.so.1:
RAC安装过程中,安装GI,运行root.sh脚本时报如下错误: # /u01/app//grid/root.sh Running Oracle 11g root script... The foll ...
- AR_标准应收过账至总账基本操作(流程)
2014-06-04 Created By BaoXinjian
- Android多点触摸放大缩小图片
1.Activity package com.fit.touchimage; import android.app.Activity; import android.graphics.Bitmap; ...
- 以 DirectUI 方式实现的ImageButton
原文链接: http://www.cnblogs.com/hoodlum1980/archive/2011/02/15/1954779.html 这是一篇比较简单的文章,主要讲解的是用 DirectU ...
- python 中hive 取日期时间的方法
#!/usr/bin/env python3 import sys import os import time, datetime sys.path.append(os.getenv('HIVE_TA ...
- php - 中文字符串分割
//先删除掉非中文的字体$str = preg_replace('/[^\x{4e00}-\x{9fa5}]/u', '', $str);//经过测试中文占3个篇幅$re = chunk_split( ...
- mod_fastcgi和mod_fcgid的区别
mod_fcgid是一个跟mod_fastcgi二进制兼容的Apache module. 原 来的mod_fastcgi因为实现方式的限制,所以可能会创建了很多不必要的进程,而实际上只需要更少的进程就 ...
- 深入理解Docker Volume(一)
想要了解Docker Volume,首先我们需要知道Docker的文件系统是如何工作的.Docker镜像是由多个文件系统(只读层)叠加而成.当我们启动一个容器的时候,Docker会加载镜像层并在其上添 ...
- git将远程仓库最新版本拉到本地仓库
一.正规做法有两种.git fetch和git pull. 注意不管用fetch还是pull,做之前都要在本地仓库做一次git commit,确保,本地仓库和工作目录及缓存一致.1.git fetch ...