创新工场笔试题整理
2013-09-13 20:56:36
转自:http://www.cnblogs.com/passingcloudss/archive/2011/05/04/2034209.html
【作者按】网上搜集的题目,自己整理了一下,写了个解答,所有程序均在VS2010上调试通过!
如果各位看官有更好更高效更巧妙的方法,请不吝指教!
【一】三道题程序题,要求一个小时做完,而且提前交卷有加分。
题目1:把一串英文句子按单词反序输出。如:"good moring" -> "moring good"。
题目2:输入一个正整数N,输出大于N且最接近这个数的素数。
题目3:用数组实现排序二叉树。
【出处】http://mcs.sysu.edu.cn/user/longt/Article_1790
【一解】
题目1:我的想法是把句子的每个字符遍历一遍,找到每个单词首字母的位置,然后再倒过来访问。
{
Node(int n, Node * p = NULL) {i = n; next = p;}
int i; // 存放单词首字母序号
Node * next;
};
{
int i =0;
Node * first = NULL;
if (sentence != NULL && sentence[0] !='\0')
{
first =new Node(0, NULL); // 判断内存是否申请成功语句此处略去
}
// 遍历寻找每个单词首字母
while (sentence[i] !='\0')
{
if (sentence[i] =='')
// 前端插入
first =new Node(i+1, first); // 判断内存是否申请成功语句此处略去
i++;
}
char* r =newchar[i+1];
Node * p = first;
int n =0;
while (p != NULL)
{
for (int j=p->i; j<i; j++,n++)
{
r[n] = sentence[j];
}
r[n++] ='';
i = p->i -1;
p = p->next;
}
r[n-1] ='\0'; // 字符串结束符
return r;
}
题目2:我的想法是写一个质数判断函数,然后从N+1往后开始寻找。感觉应该有更佳的方法。
usingnamespace std;
// 判断一个数是否是质数
bool IsPrimeNumber(int n)
{
if (n ==2)
returntrue;
if (n %2==0|| n <3)
returnfalse;
int end = (int)sqrt((float)n); // 不写在for循环判断中,提高效率
for (int i=3; i<=end; i=i+2)
{
if (n % i ==0)
returnfalse;
}
returntrue;
}
{
while (IsPrimeNumber(n+1) ==false)
n++;
return n+1;
}
题目3:我不知道用数组实现“排序二叉树”和“实现二叉树”有什么区别。我觉得如果是用数组实现二叉树的话还是很简单的,就是开一个数组,数组中每个元素存放一个树结点,每个树结点包含有“parent”、“leftchild”和“rightchild”三个int。
以下摘自原文作者:“思路: 一颗树中每个结点只有唯一一个父亲,而数组中每个元素只能存一个值,所以把这个值存为父亲结点,根节点值定-1就好。但是要求实现排序二叉树,所以还需要记录每个结点,因此用个pair<T,T>,最后通过数组pair<T,T> tree[] 来实现。”
【二】请问下面这个程序输出是什么?
union A
{
int i;
char c[2];
};
int main()
{
A a;
a.c[0] =10;
a.c[1] =1;
printf("%d", a.i);
return0;
}
【出处】http://topic.csdn.net/u/20101012/21/3aa793bb-c8e5-49c7-b72f-ca985b0d5c4e.html?32141
【二解】union A开辟了一个4字节的空间,如果是x86的电脑,那么是按小端格式(Little-endian)存储数据的,那么A.c[0]指向的就是i的最后一个字节。举列来说:如果我给A.i赋值为10,那么实际内存中是这么存储的:
(关于何为小端格式,请参见http://www.cnblogs.com/passingcloudss/archive/2011/05/03/2035273.html)
地址 0 1 2 3
数据 0x0A 0x00 0x00 0x00
c[0]=0x0A,c[1]=0x00,c[2]=0x00,c[3]=0x00;
对于未赋值的字节块,编译器在调试版本(DEBUG)会自动填充0xCC,因此本题涉及的内存实际上是这样的:
地址 0 1 2 3
数据 0x0A 0x01 0xCC 0xCC
最终输出结果应该是0xCCCC010A=-859045622。
这里再重申一下,该结果是在x86电脑的DEBUG模式下的输出!
一个诡异的发现,如果把程序改为:
union
{
int i;
char c[2];
}A;
void main()
{
A.c[0]=10;
A.c[1]=1;
printf("%d", A.i);
}
输出结果是0x0000010A=266!
引申:通过这道题的方法,我们可以判断CPU是小端格式还是大端格式!
判断CPU是little-endian还是big-endian的一个例子如下:
1 union 2 { 3 size_t i; 4 unsigned char c; 5 }C; 6 7 C.i = 0xf0f1f2f3; 8 cout<<hex<<C.i <<endl; 9 cout<<hex<<(size_t)C.c<<endl<<endl;
若为little-endian,低地址存放低位,输出应为f3;否则低地址存放高位,输出为f1
【三】笔试题量很小,答题时间1个小时。1道填空题,9道左右选择题,最后一道编程题。
题目1:对于int a = 65536 + 1024 + 8 + 1;int b = f(a);执行后b等于多少?
{
int c =0;
while(x !=0)
{
x = x & (x-1);
c++;
}
return c;
}
题目2:5个骰子,六个面分别标有1~6,现在将五个同时随机投掷,五个点数之和为下面哪个点的概率最大?
(A)14 (B)15 (C)17 (D)20
题目3:请问c等于多少?
char a =0x48;
char b =0x52;
c = b<<8| a;
题目4:编程题
A、B两个量杯,容量分别为M升、N升,现在要用A和B给另一个量杯C盛水K升,C量杯足够大,备用水无限。编程输出每一个步骤三个杯子中的水量。比如:输出(0,0,0), (M,0,0)等。
【出处】http://polaris1119.iteye.com/blog/769423
【三解】
题目1:这个程序就是求二进制数中1的个数,所以答案是4。
题目2:
思路一:先算每个骰子掷出点数的期望值。每面的概率都是1/6,单个骰子的期望为
1*1/6 + 2*1/6 + 3*1/6 + 4*1/6 + 5*1/6 + 6*1/6 = 3.5
5个骰子相互独立,期望就是3.5*5=17.5,17或18为最接近的点数,选(C)。
思路二:掷出的点数最小为5,即1+1+1+1+1只有一种可能;掷出的点数最大为30,即6+6+6+6+6也只有一种可能。
点数的分布应该是中间大、两头小,并且应该是左右对称的,所以最可能的点数应该是(5+30)/2=17.5,选(C)。
题目3:b<<8是把b向左位移8位,相当于把b乘以2的8次方,得到0x5200;
再和a位或,由于0x5200的后8位是0,位或运算等效于加法运算,得到c=0x5248=21064。
引申:如果c不是定义成unsigned long,而是一个小字节的类型,比如c定义成char型,那么c=0x48。
题目4: 这道题比较复杂,我们先来分析一下思路。首先,不失一般性,不妨设M>N,如果我们能实现 K1=K%N 升,那么只需再用B量杯给C量杯加上 (int)K/N 杯即可。
设M和N的最大公约数为L,于是,不管我们对M和N进行几次和运算或差运算,得到的数仍是L的倍数。也就是说,不管怎样操作,使用A量杯和B量杯,我们只能得到“L的倍数”升。注意,这只是说,能实现的一定是L的倍数,但不一定每个L的倍数都能实现。
另一方面,根据数论中的Bezout等式,存在整数a和b,使得aM+bN=L成立。
事实上,假如我们找到了一组满足a0*M+b0*N=L的(a0,b0),那么对于任意整数t,a=a0+t*N/L,b=b0-t*M/L都能满足aM+bN=L。
所以,我们一定能找到一组a>0、b<0,使aM+bN=L,那么我们只需往C量杯中倒入a杯A量杯的水,再倒走(-b)杯B量杯的水,C量杯中就能得到L升水!
结合上面两方面,我们得到,C量杯能实现K升水 当且仅当 K1是L的自然数倍。
具体实现的思路如下:先求出M和N的最大公约数L,判断K1是否能够整除L;然后不断给C倒入M升水(中途如果C水量超过N,就倒走N升水),直到C水量为K1;最后再给C中倒入若干次N升水即可。
具体代码如下:
{
cout << i <<".("<< A <<","<< B <<","<< C <<")"<< endl;
i++;
}
{
if (M >= N)
{
if (M%N ==0)
return N;
else
return GCD(M%N, N);
}
else
{
if (N%M ==0)
return M;
else
return GCD(M, N%M);
}
}
{
// M, N, K是三个容器的容量
// A, B, C是三个容器目前的水量
int A=0, B=0, C=0, i=1;
int K1 = K % N;
int L = GCD(M, N);
if (K1%L !=0) // 不可能实现
returnfalse;
while (C != K1)
{
// A中盛满水倒给C
A = M;
Show(i, A, B, C);
C += A; A =0;
Show(i, A, B, C);
// 如果C水量超过N,就倒走N
while (C > N)
{
B = N; C -= N;
Show(i, A, B, C);
B =0;
Show(i, A, B, C);
}
}
// 给C倒上若干杯B中的水
while (C != K)
{
B = N;
Show(i, A, B, C);
B =0; C += N;
Show(i, A, B, C);
}
returntrue;
}
事实上,这个实现方法不是很完美,比如M=5,N=4,K=10,本来只需用2杯A量杯的水即可;但是按照上述算法,是倒入一杯A量杯的水,随后立即倒走一杯B量杯的水,如此往复10次,得到10升,这显然是不明智的。其实可以在程序中加以改进,只需在每准备倒出一杯B量杯的水时判断一下:现在C中的水量离K升是不是差M的整数倍即可。
{
// M, N, K是三个容器的容量
// A, B, C是三个容器目前的水量
int A=0, B=0, C=0, i=1;
int K1 = K % N;
int L = GCD(M, N);
if (K1%L !=0) // 不可能实现
returnfalse;
while (C != K1)
{
// A中盛满水倒给C
A = M;
Show(i, A, B, C);
C += A; A =0;
Show(i, A, B, C);
// 如果C水量超过N,就倒走N
while (C > N)
{
if ((K-C)%M ==0) // 改进点
break;
B = N; C -= N;
Show(i, A, B, C);
B =0;
Show(i, A, B, C);
}
if ((K-C)%M ==0) // 改进点
break;
}
if ((K-C)%M ==0)
{
// 给C倒上若干杯A中的水,改进点
while (C != K)
{
A = M;
Show(i, A, B, C);
A =0; C += M;
Show(i, A, B, C);
}
}
else
{
// 给C倒上若干杯B中的水
while (C != K)
{
B = N;
Show(i, A, B, C);
B =0; C += N;
Show(i, A, B, C);
}
}
returntrue;
}
这样的算法就比较完美了!
【四】一些小题目
题目1:路由器与交换机的区别。
题目2:进程与线程的差别:
A、操作系统只调度进程,不调度线程
B、线程共享内存地址空间,进程不共享
C、线程间可以共享内存数据,但进程不可以
D、进程间可以通过IPC通信,但线程不可以
题目3:下面排序算法的时间复杂度不是O(nlogn)的是:
A、二分法插入排序 B、快速排序 C、归并排序 D、堆排序
题目4:利用KMP算法实现字符串匹配。
题目5:某工作有5道工序,某个工作不能在最后做,请问有多少种工作情况。
题目6:内存中有3页,初始为空,页面走向为4,3,2,1,4,3,5,4,3,2,1,5,分别使用先进先出,最近最少使用,理想页面置换算法,请问缺页次数是多少?
题目7:TCP具有但UDP不具有的特点不包括:
A、对上层应用而言,收到数据包的顺序与对方发送的顺序一致
B、源IP、目的IP均相同的数据包经过同样的路由路径
C、传输过程中个别数据包丢失,接收端存在检测机制
D、传输数据前必须使用握手方式建立连接
题目8:下面程序的输出结果是什么?
int func(int n)
{
if(n==0)
return3;
else
return (func(n-2)+func(n-1));
}
void main()
{
printf("%d\n", func(13));
}
【出处】http://hi.baidu.com/g882/blog/item/5142211f3d1d2efae0fe0b94.html
【四解】
题目1:路由器和交换机的区别
[1]工作层次不同:交换机是工作在OSI/RM开放体系结构的数据链路层(即第二层),而路由器工作在OSI模型的网络层(即第三层)。
[2]转发所依据的对象不同:交换机是利用物理地址或者说MAC地址来确定转发数据的目的地址;而路由器则是利用不同网络的ID号(即IP地址)来确定数据转发的地址。
[3]传统的交换机只能分割冲突域,不能分割广播域;而路由器可以分割广播域。
[4]路由器提供了防火墙的服务,而交换机则没有。
参考:http://network.51cto.com/art/200806/78135.htm
题目2:
选项A:线程有两种传统的控制模式:用户级线程和内核级线程,前者对于操作系统内核是不可见的,而对于后者,内核了解每一个作为可调度实体的线程。所以内核级线程可被操作系统调度。A错!
选项B:正确!
选项C:进程间虽然不能共享内存地址空间,但是把内存数据复制一份,达到共享数据的目的。 C错!
选项D:进程间通过IPC通信,线程间可以直接读写进程数据段(如全局变量)来进行通信。D错!
题目3:各种排序复杂度总结如下:
O(n^2):冒泡排序、选择排序、插入排序
O(nlogn):堆排序、快速排序
O(n):归并排序
本题选择(A)。
题目4:略。
题目5:没有限制“某个工作不能在最后做”的情况下共有 5!=120 种工作方案,去除“某个工作在最后做”的情况 4!=24 种工作方案,最后得到96种工作方案。
题目6:
(1)先进先出:4,3,2,1(miss),4(miss),3(miss),5(miss),4,3,2(miss),1(miss),5。
缺页次数6次。
(2)最近最少使用:4,3,2,1(miss),4(miss),3(miss),5(miss),4,3,2(miss),1(miss),5(miss)。
缺页次数7次。
(3)理想页面置换算法:4,3,2,1,4,3,5,4,3,2,1,5,
关于这三种算法,下面给出计算机操作系统 第三版-汤子瀛-西电出版上的例子:
1.最佳(Optimal)置换算法
最佳置换算法是由Belady于1966年提出的一种理论上的算法。其所选择的被淘汰页面,
将是以后永不使用的,或许是在最长(未来)时间内不再被访问的页面。采用最佳置换算法,
通常可保证获得最低的缺页率。但由于人们目前还无法预知一个进程在内存的若干个页面
中,哪一个页面是未来最长时间内不再被访问的,因而该算法是无法实现的,但可以利用
该算法去评价其它算法。现举例说明如下。
假定系统为某进程分配了三个物理块,并考虑有以下的页面号引用串:
7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1
进程运行时,先将7,0,1三个页面装入内存。以后,当进程要访问页面2时,将会
产生缺页中断。此时OS根据最佳置换算法,将选择页面7予以淘汰。这是因为页面0将作
为第5个被访问的页面,页面1是第14个被访问的页面,而页面7则要在第18次页面访
问时才需调入。下次访问页面0时,因它已在内存而不必产生缺页中断。当进程访问页面3
时,又将引起页面1被淘汰;因为,它在现有的1,2,0三个页面中,将是以后最晚才被
访问的。图4-26示出了采用最佳置换算法时的置换图。由图可看出,采用最佳置换算法发
生了6次页面置换。
2.先进先出(FIFO)页面置换算法
这是最早出现的置换算法。该算法总是淘汰最先进入内存的页面,即选择在内存中驻
留时间最久的页面予以淘汰。该算法实现简单,只需把一个进程已调入内存的页面,按先
后次序链接成一个队列,并设置一个指针,称为替换指针,使它总是指向最老的页面。但
该算法与进程实际运行的规律不相适应,因为在进程中,有些页面经常被访问,比如,含
有全局变量、常用函数、例程等的页面,FIFO算法并不能保证这些页面不被淘汰。
这里,我们仍用上面的例子,但采用FIFO算法进行页面置换(图4-27)。当进程第一次
访问页面2时,将把第7页换出,因为它是最先被调入内存的;在第一次访问页面3时,
又将把第0页换出,因为它在现有的2,0,1 三个页面中是最老的页。由图4-27 可以看出,
利用FIFO算法时进行了12次页面置换,比最佳置换算法正好多一倍。
3.LRU(Least Recently Used)置换算法
FIFO置换算法性能之所以较差,是因为它所依据的条件是各个页面调入内存的时间,
而页面调入的先后并不能反映页面的使用情况。最近最久未使用(LRU)的页面置换算法,是
根据页面调入内存后的使用情况进行决策的。由于无法预测各页面将来的使用情况,只能
利用“最近的过去”作为“最近的将来”的近似,因此,LRU置换算法是选择最近最久未
使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访
问以来所经历的时间t,当须淘汰一个页面时,选择现有页面中其t 值最大的,即最近最久
未使用的页面予以淘汰。
利用LRU算法对上例进行页面置换的结果如图4-28 所示。当进程第一次对页面2进
行访问时,由于页面7是最近最久未被访问的,故将它置换出去。当进程第一次对页面3
进行访问时,第1页成为最近最久未使用的页,将它换出。由图可以看出,前5个时间的
图像与最佳置换算法时的相同,但这并非是必然的结果。因为,最佳置换算法是从“向后
看”的观点出发的,即它是依据以后各页的使用情况;而LRU算法则是“向前看”的,即
根据各页以前的使用情况来判断,而页面过去和未来的走向之间并无必然的联系。
先进先出页面置换方案工作过程解释如下:
内存中有3页,按照走向4,3,2,1,4,3,5,4,3,2,1,5。
- 下一个页面为4,将4调入,此时页面FIFO为4;
- 下一个页面为3,将3调入,此时页面FIFO为4,3;
- 下一个页面为2,将2调入,此时页面FIFO为4,3,2;
- 下一个页面为1,FIFO 中没有1,发生缺页中断,因为是先进先出,将4换出,将1调入,此时页面FIFO为3,2,1;
- 下一个页面为4,FIFO中没有4,发生缺页中断,因为是先进先出,将3换出,将4调入,此时页面FIFO为2,1,4;
- 下一个页面为3,FIFO中没有3,发生缺页中断,因为是先进先出,将2换出,将3调入,此时页面FIFO为1,4,3;
- 下一个页面为5,FIFO中没有5,发生缺页中断,因为是先进先出,将1换出,将5调入,此时页面FIFO为4,3,5;
- 下一个页面为4,FIFO中有4,无需页面置换,此时页面FIFO为4,3,5;
- 下一个页面为3,FIFO中有3,无需页面置换,此时页面FIFO为4,3,5;
- 下一个页面为2,FIFO中没有2,发生缺页中断,因为是先进先出,将4换出,将2调入,此时页面FIFO为3,5,2;
- 下一个页面为1,FIFO中没有1,发生缺页中断,因为是先进先出,将3换出,将1调入,此时页面FIFO为5,2,1;
- 下一个页面为5,FIFO中有5,无需页面置换,此时页面FIFO为5,2,1.
综上一共发生了6次缺页中断。
最近最少使用(LRU)面置换方案工作过程与上面的类似,只不过是在发生缺页时,将最近最少未使用的换出,此处不再给出详细过程。
- 下一个页面为4,将4调入,此时内存中页面为4;
- 下一个页面为3,将3调入,此时内存中页面为4,3;
- 下一个页面为2,将2调入,此时内存中页面为4,3,2;
- 下一个页面为1,FIFO 中没有1,发生缺页中断,因为是LRU,将2换出,将1调入,此时页面FIFO为4,3,1;
- 下一个页面为4,FIFO中有4,此时页面FIFO为3,1,4;
- 下一个页面为3,FIFO中有3,此时页面FIFO为1,4,3;
- 下一个页面为5,FIFO中没有5,发生缺页中断,因为是LRU,将3换出,将5调入,此时页面FIFO为1,4,5;
- 下一个页面为4,FIFO中有4,无需页面置换,此时页面FIFO为1,5,4;
- 下一个页面为3,FIFO中没有3,发生缺页中断,因为是LRU,将4换出,将3调入,此时页面FIFO为1,5,3;
- 下一个页面为2,FIFO中没有2,发生缺页中断,因为是LRU,将4换出,将2调入,此时页面FIFO为1,5,2;
- 下一个页面为1,FIFO中有1,此时页面FIFO为5,2,1;
- 下一个页面为5,FIFO中有5,无需页面置换,此时页面FIFO为2,1,5.
理想页面置换算法,



浙公网安备 33010602011771号