2014阿里巴巴面试题哈尔滨
1. 单选题
1. 假设把整数关键码K散列到N个槽列表,以下哪些散列函数是好的散列函数
A: h(K)=K/N;
B: h(K)=1;
C: h(K)=K mod N;
D: h(K)=(K+rand(N)) mod N, rand(N)返回0到N-1的整数
答案:C,选项D不能保证Hash表在O(1)的时间内寻址
2. 下面排序算法中,初始数据集的排列顺序对算法的性能无影响的是:
A: 堆排序 B:插入排序
C: 冒泡排序 D:快速排序
答案:a ,堆排序初始数据有序时仍要构建最大(最小)堆。(1)堆排序的时间复杂度一直都是O(nlogn),不稳定(2)插入排序在初始有序情况下,时间复杂度为O(n),平均时间复杂度为O(n^2),稳定排序(3)冒泡排序在初始有序的情况下,增加交换标示flag可将时间复杂度降到O(n),稳定排序(4)快速排序在初始有序的情况下,可能会退化到O(n^2),不稳定排序
3. 下面说法错误的是:
A: CISC计算机比RISC计算机指令多
B: 在指令格式中,采用扩展操作码设计方案的目的是为了保持指令字长不变而增加寻址空间
C:增加流水线段数理论上可以提高CPU频率
D:冯诺依曼体系结构的主要特征是存储程序的工作方式
答案:b,原因 扩展操作码的设计方案目的是保持指令字长度不变而增加指令操作的数量
4. 不属于冯诺依曼体系结构必要组成部分是:
A:CPU B: Cache C:RAM D:ROM
答案:选b ,冯诺依曼模型由控制器、存储器、运算器、输入、输出构成,cache是后来提出的概念。
5. 一个栈的入栈序列式ABCDE则不可能的出栈序列是:
A:DECBA B:DCEBA C:ECDBA D:ABCDE
答案:c
6.你认为可以完成编写一个C语言编译器的语言是:
A:汇编 B:C语言 C:VB D:以上全可以
答案:D,任何一种语言都可以实现编译器的功能,只不过效率有高低之分,汇编的效率最高。
7. 关于C++/JAVA类中的static成员和对象成员的说法正确的是:
A:static成员变量在对象构造时候生成
B: static成员函数在对象成员函数中无法调用
C: 虚成员函数不可能是static成员函数
D: static成员函数不能访问static成员变量
答案:c 解释,(1)类的static成员变量属于该抽象类,需要在类定义时初始化,不可以在对象的构造函数中初始化。(2) static成员函数在对象成员函数中可以调用,同属于一个类作用域 (3)正确,static成员函数不可以声明为const和virtual,详见C++ primer (4)static成员函数只能够访问static 成员变量。
8:看不清
9:某进程在运行过程中需要等待从磁盘上读入数据,此时进程的状态将:
A: 从就绪变为运行 B:从运行变为就绪
C: 从运行变为阻塞 D:从阻塞变为就绪
答案:c ,I/O操作会使进程由运行态转为阻塞。
10:下面算法的时间复杂度为:
Int f(unsigned int n)
{
If(n==0||n==1)
Return 1;
Else
Return n*f(n-1);
}
A: O(1) B:O(n) C:O(N*N) D:O(n!)
答案:b ,求n!的递归运算,其中进行了N次算术乘法,故时间复杂度o(n) 。
11: n从1开始,每个操作可以选择对n加1或者对n加倍。若想获得整数2013,最少需要多少个操作。
A:18 B:24 C:21 D;不可能
答案: a ,可以把2013倒推,奇数减1,偶数除以2,最后可以推出需要18步。
12:对于一个具有n个顶点的无向图,若采用邻接表数据结构表示,则存放表头节点的数组大小为:
A: n B: n+1 C: n-1 D:n+边数
答案:a ,邻接矩阵表示,每个顶点构成一个链表。
13:不全
14:如下函数,在32bit系统foo(2^31-3)的值是:
Int foo(int x)
{
Return x&-x;
}
A: 0 B: 1 C:2 D:4
答案:c,这题实际上求2^31-3 补码最低位“1”所处的位置,选择C是应为"^"在函数体内表示的是异或运算符,且"-"优先级高于^,所以2^31-3=28,补码形式000……001110。
15:对于顺序存储的线性数组,访问节点和增加节点删除节点的时间复杂度为:
A: O(n),O(n) B:O(n),O(1) C:O(1),O(n) D:O(n),O(n)
答案:c
16:在32为系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是:
Struct A
{
Int a;short b;int c;char d;
};
Struct B
{int a;short b;char c;int c;};
A: 16,16 B:13,12 C:16,12 D:11,16
答案:c,结构体的内存的对齐方式,字节对齐包含了每个变量自身对齐和复杂类型整体对齐。
17:袋中有红球,黄球,白球各一个,每次任意取一个放回,如此连续3次,则下列事件中概率是8/9的是:
A: 颜色全相同 B:颜色全不相同C:颜色不完全相同 D:颜色无红色
答案:选c,颜色全相同的概率为1/3*1/3*1/3×3=1/9,8/9概率的提法应该是 颜色不完全相同的概率。
18:一个洗牌程序的功能是将n张牌的顺序打乱,以下关于洗牌程序的功能定义说法最恰当的是:
A: 每张牌出现在n个位置上的概率相等
B: 每张牌出现在n个位置上的概率独立
C: 任何连续位置上的两张牌的内容独立
D: n张牌的任何两个不同排列出现的概率相等
答案:有争议,个人看法选D,具体看洗牌程序的实现。假如是54张牌排列在array[54]中,则第一张牌随机数法选择任何一个位置的概率为1/54,但是后续牌的可选位置少于54,要受到前面牌占位的影响,这不是有放回的排列组合事件,因此后续牌的占位也不是独立的。
19:用两种颜色去染排成一个圈的6个棋子,如果通过旋转得到则只算一种,一共有多少种染色:
A: 10 B:11 C:14: D:15
答案:应该是C
假设颜色是红色和蓝色,用(m,n)表示m个红色,n个蓝色: (6,0),(0,6)各1种; (5,1),(1,5)各1种; (4,2),(2,4)各3种; (3,3)有4种; 一共就是2×(1+1+3)+4=14种
20:递归式的先序遍历一个n节点,深度为d的二叉树,则需要栈空间的大小为:
A: O(n) B:O(d) C:O(logn) D:(nlogn)
答案:b,如果考虑到最好和最坏情况则是O(logn)和O(n),其实就是二叉树的深度d。
很显然遍历二叉树的基本操作就是访问其结点,无论采用哪种方法所需的时间复杂度都是O(n)。所需的辅助空间的容量就是遍历过程中栈的最大容量,即树的深度。
第二部分:多选
21:两个线程运行在双核机器上,每个线程主线程如下,线程1:x=1;r1=y;线程2:y=1;r2=x;
X和y是全局变量,初始为0。以下哪一个是r1和r2的可能值:
A: r1=1,r2=1
B: r1=1,r2=0
C:r1=0,r2=0
D:r1=0,r2=1
答案:ABD,解释:r1和r2不可能同时为0,当一个有赋值时,必然完成了对另一个x或y的赋值
22.关于Linux系统的负载,以下表述正确的是:
A: 通过就绪和运行的进程数来反映
B: 通过TOP命令查看
C: 通过uptime查看
D: Load:2.5,1.3,1.1表示系统的负载压力在逐渐变小
23:关于排序算法的以下说法,错误的是:
A: 快速排序的平均时间复杂度O(nlogn),最坏O(N^2)
B:堆排序平均时间复杂度O(nlogn),最坏O(nlogn)
C:冒泡排序平均时间复杂度O(n^2),最坏O(n^2)
D:归并排序的平均时间复杂度O(nlogn),最坏O(n^2)
答案: d,归并排序的平均时间复杂度最坏也是O(nlogn)
24:假设函数rand_k会随机返回一个【1,k】之间的随机数(k>=2),并且每个证书出现的概率相等。目前有rand_7,通过调用rand_7()和四则运算符,并适当增加逻辑判断和循环控制逻辑,下列函数可以实现的有:
A:rand_3 B:rand_21 C:rand_23 D:rand_49
答案:D是肯定的 rand_49=rand_7^2=7*(rand_7-1)+rand_7 ,只用四则运算符的话其他选项则不好实现。总结:用rand_a构造rand_b,在不限制运算符的情况下,(1)a>b时一定可以用rand_a构造rand_b,只需判定rand_a()中小于b的数返回;(2)a<b时,则需要先构造rand_a^2=a*(rand_a-1)+rand_a,直到a^2>b,这样使用(1)的方法即可。
参考:http://hawstein.com/posts/19.10.html
填空和问答
解答:前序为 根左右,后序为左右根,因此可以判断数的跟结点和最左孩子等,可以推导出中序遍历顺序:a+b*c-d-e/f 。
26.某缓存系统采用LRU,缓存容量为4,并且初始为空,那么在顺序访问以下数据项的时候:1,5,1,3,5,2,4,1,2
出现缓存直接命中的次数为:(),最后缓存即将淘汰的是()
答案:3,5
分析:LRU是Least Recently Used近期最少使用算法,实现策略是把内存中最长没使用过的页面置换出去。
- 1调入内存:1
- 5调入内存:1 5
- 1调入内存:5 1 (命中1)
- 3调入内存:5 1 3
- 5调入内存:1 3 5(命中5)
- 2调入内存:1 3 5 2
- 4调入内存:3 5 2 4 (1最久未使用调出置换4)
- 1调入内存:5 2 4 1 (3最久未使用调出置换1)
- 2调入内存:5 4 1 2 (命中2)
- 因此命中了3次,内存中即将淘汰的是5
1调入内存:1 5调入内存:1 5 1调入内存:5 1 (命中1) 3调入内存:5 1 3 5调入内存:1 3 5(命中5) 2调入内存:1 3 5 2 4调入内存:3 5 2 4 (1最久未使用调出置换4) 1调入内存:5 2 4 1 (3最久未使用调出置换1) 2调入内存:5 4 1 2 (命中2) 因此命中了3次,内存中即将淘汰的是5
27.两个较长的单链表a和b,为了找出节点node满足node in a并且node in b。请设计空间使用尽量小的算法。
解答:求两个链表的公共节点题目,这题的关键是两个链表是否相交。就是先分别遍历一遍链表A和链表B,在遍历时分别记下链表AB的长度,并且在最后看看链表A和链表B的最后一个节点是不是相同,如果相同则有公共节点,如果不同就没有公共节点。找公共节点就是再利用两个指针,根据遍历时记录的长度,找到第一个公共节点,这个节点后面的就都是公共节点了。
(1)如果相交的话可以得出空间复杂度O(1),时间复杂度O(m+n)的算法:求得链表长度length_a和length_b,假设链表a比较长,则a链表指针先走(length_a-length_b)的步长,随后两链表指针同步向后遍历,当出现结点相等的接点就是相交点。参考:http://blog.csdn.net/hackbuteer1/article/details/7583102 和http://ngloom.me/2011/09/26/find_common_node_of_2_lists/
- #include <iostream>
- using namespace std ;
- struct node
- {
- int v;
- node *next;
- };
- /* 返回链表的长度 链表为空 返回0
- */
- size_t listLen(node * p)
- {
- size_t num = 0;
- while (p!=NULL)
- {
- num++;
- p = p->next;
- }
- return num;
- }
- // 如果找到了 则返回指针 指向公共节点
- // 如果不存在 则返回空指针
- node * findFirstCommenNode(node * pheada, node * pheadb)
- {
- size_t lenA = listLen(pheada);
- size_t lenB = listLen(pheadb);
- node * plistA = pheada;
- node * plistB = pheadb;
- //调整长度
- //plistA 指向较长的一个
- if (lenA < lenB)
- {
- plistB = pheada;
- plistA = pheadb;
- size_t t = lenA;
- lenA = lenB;
- lenB = t;
- }
- while(lenA > lenB)
- {
- plistA = plistA->next;
- --lenA;
- }
- //一样长了
- //寻找公共节点
- while (plistA!=NULL && plistA != plistB)
- {
- plistA = plistA->next;
- plistB = plistB->next;
- }
- return plistA;
- }
#include <iostream> using namespace std ; struct node { int v; node *next; }; /* 返回链表的长度 链表为空 返回0 */ size_t listLen(node * p) { size_t num = 0; while (p!=NULL) { num++; p = p->next; } return num; } // 如果找到了 则返回指针 指向公共节点 // 如果不存在 则返回空指针 node * findFirstCommenNode(node * pheada, node * pheadb) { size_t lenA = listLen(pheada); size_t lenB = listLen(pheadb); node * plistA = pheada; node * plistB = pheadb; //调整长度 //plistA 指向较长的一个 if (lenA < lenB) { plistB = pheada; plistA = pheadb; size_t t = lenA; lenA = lenB; lenB = t; } while(lenA > lenB) { plistA = plistA->next; --lenA; } //一样长了 //寻找公共节点 while (plistA!=NULL && plistA != plistB) { plistA = plistA->next; plistB = plistB->next; } return plistA; }
(2)如果两个链表不是相交问题,那么就只能采用蛮力法了O(m*n) 。
28.存储数据量超出单节点数据管理能力的时候,可以采用的办法有数据库sharding的解决方案,也就是按照一定的规律把数据分散存储在多个数据管理节点N中(节点编号为0,1,2,,,,N-1)。假设存储的数据时a 请完成为数据a计算存储节点的程序。
- #define N 5
- int hash(int element){
- return element*2654435761;
- }
- int shardingIndex(int a){
- int p = hash(a);
- _________________________; //这里是空格
- return p;
- }
#define N 5 int hash(int element){ return element*2654435761; } int shardingIndex(int a){ int p = hash(a); _________________________; //这里是空格 return p; }解答:需要最P做处理,使hash值映射到(0,1,2,,,,N-1), p=p%N。
3、(8分)宿舍内5个同学一起玩对战游戏。每场比赛有一些人作为红方,另一些人作为蓝方。请问至少需要多少场比赛,才能使任意两个人之间有一场红方对蓝方和蓝方对红方的比赛?
分析:这题正确答案应该是4,如果用推导的方法获得递推公式
简化为
所以T(5)=T(3)+2=T(2)+4=T(1)+6,T(1)=0; T(5)=6;
但实际上我们可以举出只需要四场比赛的例子。第一场: 红方为ABE,蓝方为CD
第二场:红方为CDE,蓝方为AB
第三场:红方为AC,蓝方为BDE
第四场:红方为BD,蓝方为ACE
4、一个有10亿条记录的文本文件,已按照关键字排好序存储。请设计算法,可以快速的从文件中查找指字关键字的记录
分析:有10亿条记录的文本文件存储在磁盘中,不能一次存入内存,所以讲10亿条分为N份,然后顺序将第N份存入内存,对关键字进行哈希得到ID号,然后进行二分法比较,如果不在这一份之中,那么需要将磁盘中的第二份导入内存,就这样进行计算~~