十月份第二周笔试题

这一周是国庆长假结束之后的一周,先后参加了宇龙酷派C++工程师的面试(一面、二面、签约见面会)、浙江大华windows开发工程师(笔试和一、二、三面)、360的windows开发工程师的笔试、29所的面试、百度测试工程师的笔试和一面,相当的累,各种赶场子,也收集了一些笔试题和面试中的现场笔试题,总结一下:

【宇龙酷派】

当时投宇龙酷派,完全是随便乱投的,看到有一个C++工程师的岗位,然后觉得挺适合的就投了,完了就是它们HR打电话通知我去一面,当时觉得很蹊跷,因为投研发岗的好像正在笔试,也没多想就去了,在三教一楼的一个小教室里,里面有两个面试官,其中一个是面技术的,等到面我的时候,我好奇的问了一下他为什么不用笔试,他说他更喜欢面试的时候现场在电脑上写程序,中间他还问了我一些C++的基础知识,和java的一些区别,已经一些非技术类的问题,有点聊天的意思,然后就对着我的简历问了几个问题,说尽量会把我的简历递给上面的人筛选,然后让我等通知,果然第二天就收到让我二面的通知,二面的时间正好和我去浙江大华面试冲突了,最后赶在大华面试结束后,打个飞的过去,和他们HR的副总监进行了二面,问了我是否挂过科,哪里人,“软件设计师”是什么水平?(哈哈)。然后就让我等着最后发offer。第二天果然就发了,让晚上5点半过去开个见面会,会上又是那个HR副总监,在那大谈特谈,各种“洗脑”,然后下面坐着的十多个科大同学都在盘算,赶紧讲完,去参加360的笔试。反正也算是第一个offer了,555~

 

【浙江大华】

大华算是这一周走求职招聘流程最完整的一个企业了:先是10月9日下午2点去听他家的宣讲,然后10号去合工大(本部)参加下午两点半的笔试(话说这是我第一次去合工大,感觉校园相当不错,妹子也多,有活力),通过笔试之后,11号下午3点又跑到合工大去面试,结果(估计笔试成绩太低)等到6点,他们工作人员都快要吃晚饭了,我才赶上一面,面试的老头挺好的,问了我很多windows开发的东东,比如多线程用过哪些函数,智能指针,对话框函数,做过的项目,开发一个服务器程序最重要的几个部分,对本专业和求职意向的差异,一面差不多快20多分钟才结束。此时我已经饥肠辘辘了,问了一个HRmm说待会马上给我安排二面,然后我说晚上有急事(实际上是酷派的终面),然后她说让我先等等,还说她多订了一份饭,让给我吃,我当时非常尴尬,旁边的哥们也瞎起哄,让我赶紧吃吧,我就只好吃着了,勉强吃完只好,面试官们也开始了二面,果然我排在了其他人的前面,第一个二面,二面的面试官就显得比较严肃和雷厉风行,主要是问我笔试的两个算法题的情况,然后又出了个和strcpy()函数相关的问题问我,再然后就又问我的编程风格呐,balabala....,最后在外面等着三面,中间酷派的HR打电话过来催了一次,我说马上,然后终面的HR就对着我的简历看了又看,问了我的成绩,做的项目,导师的评价,然后中间知道他居然也是湖北人,又问了我期望的薪水,我说个了个8000(要知道那可是在杭州啊!),然后HR说月底之前给通知(十天后给了通知),风风火火的浙江大华求职就这么结束了。

大华的笔试题考了太多的结构体(混着联合体)的字节对齐问题,至少有如下的三道:

1)在32位系统中,有以下结构体,问:sizeof(struct SA)的值是:

struct SA{
int a1:8;
int a2:8;
char a3[2];
char a4[2];
};


在32位的win7上用vs2012运行可以得到如下结果:

之所以出现结构体对齐的概念,主要是为了方便CPU寻址,或者说是内存对齐。这样,即使是同样的结构体,成员换了顺序,大小就不同了,内存对齐的规则大致有如下5点:

1、内存对齐与编译器设置有关,首先要搞清编译器的默认值是多少?

2、可以通过如下设定来改变编译器内存对齐默认值:

#pragma pack(n)


3、每个结构体变量对齐,如果对齐参数n(编译器默认或者通过#pragma编译指令指定)大于该变量所占字节数m,那么就按照m对齐,那么就按照m对齐,内存偏移后的地址是m的倍数,否则是按照n对齐,内存偏移后的地址是n的倍数。(最小化长度规则)

4、结构体总大小:对齐后的长度必须是成员中最大的对齐参数的整数倍。最大对齐参数是从第三步得到的。

5、补充:如果结构体A中还有结构体B,那么B的对齐方式按照选B自己里面最长的成员来对齐。

 

此外,该结构体中还出现了位域,对于情况,相当的特殊,有如下的处理原则:

C99规定int、unsigned int和bool可以作为位域类型。但编译器几乎都对此作了扩展,允许其它类型类型的存在。
如果结构体中含有位域(bit-field),总结规则如下
1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式(不同位域字段存放在不同的位域类型字节中),Dev-C++和GCC都采取压缩方式;
4)如果位域字段之间穿插着非位域字段,则不进行压缩;
5) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,且应尽量节省内存。

 

因此根据上述的几点原则,a1和a2都要对齐到1上,即“该数据的存放地址%1==0”,偏移地址分别为0和1;

a2和a3分别对齐到2和4上,但对与结构体的总大小,不应该为(1+1+2+2=6),因为要“结构体的总大小%sizeof(int)==0”,因此结构的总大小为8!

更多和更具体的内容可以参考这篇文章:http://www.cnblogs.com/alex-tech/archive/2011/03/24/1993856.html

注意到,该结构体中即使出现了数组,对上面的规则5)也没有影响,比如,对上面的结构体做如下修改:

struct SB{
int a1:8;
int a2:8;
char a3[5];
char a4[2];
};

则sizeof(struct SB)为:12

2)在32位的操作系统中,设有以下说明和定义:

typedef union
{
	long i;
	int k[5];
	char c;
}DATE;

struct data
{
	int cat;
	DATE cow;
	double dog;
};


则,运行如下语句的结果是:

printf("sizeof(struct data)+sizeof(DATE) is %d\n",sizeof(struct data)+sizeof(DATE));


在该例子中出现了union,union的长度取决于其中的长度最大的那个成员变量的长度。即union中成员变量时重叠摆放的,其开始地址相同。

也即,union(共用体)的各个成员是以同一个地址开设存放的,每一时刻只能存储一个成员,这样就要求它在分配内存单元的时候要满足两点

1、一般而言,共用体类型实际暂用存储空间为其最长的成员所占的存储空间;

2、若是该最长的存储空间对其他成员的元类型(如果是数组,取其类型的数据长度,如下例 int a[5]的元类型长度为4),则该最大空间自动延伸!

union   mm{      
char   a;//元长度1      
int   b[5];//元长度4      
double   c;//元长度8      
int   d[3];      
};  


计算sizeof(union mm)则为24而非20!因为20%8!=0.

则上述题2)语句的输出应为:sizeof(DATE)=20 (其中最宽的类型都只占4个字节),再计算struct data,分成两部分,1)cat和cow (都是按4个字节对齐)占4+20个字节,2)24%8==0,则dog偏移地址为24。1)和2)两部分共占24+8个字节;则sizeof(struct data)+sizeof(DATE)=32+20=52

更多请参看:http://pppboy.blog.163.com/blog/static/30203796201082494026399/

3)定义联合体:

typedef union
{
	double a;
	int b;
	char c[16];
}U;


则sizeof(U)的结果为    16  

运行结果如下:

4)在C语言中,要求运算数必须是整型的运算符包括:求余运算符(%)、按位操作符(<<,>>,~,&,|和^)等

大华的两道很普通的算法题我基本都做出来了,但是给我的分数却不高,主要是可能笔试题注重基础,不要过多使用库函数,此外也要考虑算法复杂度的问题,尽量采用O(n)以下复杂度的算法,很忌讳多层嵌套的循环!

1)输入一个字符串,请编程分析该字符串中出现频率最高的字母及其出现的次数,这里不考虑有多个字母同时出现次数最多的情况,比如"abcaabcad",返回结果为'a',次数为4。

函数声明如下:

int ckstr(const char *msg, char *ret,int *times);

msg:输入参数,输入的字符串;

ret:输出参数,出现频率最高的字母;

times:输出参数,出现的次数。

返回值:成功返回0,失败返回-1。

最初我写的算法是用的一个两层循环,然后不停更新ret和times的值,直到字符串结束,后来和一个大牛同学讨论之后,感觉是可以用映射的,这样可以把复杂度降到O(n),具体的算法如下:

#include<cstring>
#include<climits>
#include<iostream>
int ckstr(const char *msg, char *ret,int *times){
int map[128];
memset(map,0,sizeof(map));
const char *p=msg;
if(p==NULL)
	return -1;
else
{
	while(*p){  //字符串未结束
		if((map[*p]+=1)>INT_MAX)  //超出整型范围,失败
			return -1;
		++p;
}
int index=0;
*ret=static_cast<char>(index-0);
*times=map[index];
for(index=1;index<128;index++)
	if(map[index]>*times) {
		*ret=static_cast<char>(index-0);
		*times=map[index];
	}
	return 0;
}
return -1; //无异常情况,不会执行到这一步
}

int main()
{
	const char str[]="abcaabcad";
	char ret='\0';
	int times=0;
	if(0==ckstr(str,&ret,×))
		std::cout<<"most use:"<<ret<<std::endl
		<<"most times:"<<times<<std::endl;
	else 
		std::cout<<"error occurs,please check!"<<std::endl;
	system("pause");
	return 0;
}


也就是将可取的128个ascii码做映射,统计各出现字符的次数,找出其中的最大值即可!

看到网上一个相似的版本,用到了STL函数std::max_element<>():

#include<iostream>
#include<cstring>
#include<algorithm>
void GetMostUseChar(const char* str){
	enum{BUF_SIZE=256};
	int map[BUF_SIZE];
	memset(map,0,sizeof(map));
	const char *p=str;
	while(*p){
		map[*p]++;
		++p;
	}
	int *TheMaxPos=std::max_element(map,map+BUF_SIZE);
	int nMaxCount=*TheMaxPos;
	char chMostUse=TheMaxPos-map;
	std::cout<<"most use:"<<chMostUse<<std::endl
		<<"use times:"<<nMaxCount<<std::endl;
}

int main()
{
	const char str[]="abcaabcad";
	GetMostUseChar(str);
	system("pause");
	return 0;
}


2)请把点分十进制的IPV4地址字符串,转换为16进制的数值,比如将192.168.1.10转换成0xC0A8010A;

函数声明如下:

int ip2val(const char *ip,unsigned int *val);


ip:输入参数,格式为点分十进制,假设IP地址合法;

val:输出参数,16进制;

返回值:成功返回0,失败返回-1.

这道题,我直接使用了C库函数sscanf(),结果被判1分!实际上是考察将字符串转换成给定进制的数字!新的思路是用一个数组表示该ipv4的四个值部分:

#include<cstdlib>
#include<cstdio>
#include<cstring>
int ip2val(const char *ip,unsigned int *val){
	const char *p=ip;
	unsigned int temp[4];
	memset(temp,0,sizeof(temp));

	if(ip==NULL) return -1;
	else{
			for(int i=0;i<4;i++){

			while(*p!='.'&& *p!='\0'){
				temp[i]=(*p-48)+10*temp[i];
				++p;
			}
			++p;
	}
 *val=(temp[0]<<24) + (temp[1]<<16) + (temp[2]<<8) + temp[3];

 return 0;
}
return -1; //不会运行到这,否则出现异常!
}

int main()
{
	const char str[]="192.168.1.10";
	unsigned int val=0;
	if(0==ip2val(str,&val))
		printf("0x%X\n",val);
	else
		printf("error exist!");
	system("pause");
	return 0;
}


【360】

奇虎的笔试现场真的是非常混乱,各路霸笔的通通到齐,前面的选择题大概有10道智力和逻辑题,比如小白鼠“试毒”,检测次品,此外还有有限状态机,正则表达式
的题,很繁很杂,引起我兴趣的是最后那道编程题:传教士和野人的过河问题!令人惊讶的是随便搜索一下,百度文库居然给了两种算法

大致的题目如下:

传教士和野人问题(The Missionaries and Cannibals Problem).在河的左岸有M个传教士,1条船和C个野人。传教士们想用这条船将所有的成员运过河取,但是受到一下条件的限制:

1)传教士和野人都能划船,但船一次最多只能装运两个;

2)在任一岸边野人的数目都不得超过传教士,否则传教士就会遭遇危险,被野人吃掉;

3)此外,假定野人会服从任何一种过河安排。

试给出衣蛾确保全部成员安全过河的方案。

 

【百度】

由于我投的是百度的测试岗,所以试题算是相当简单的,考察的范围包括数据库、网络的基础知识和一些简答的算法题,大概能记得的题目有下面几道,研发的题居然在百度文库上找到了

1)TCP/IP的四层结构:

TCP/ip协议栈分为四层:物理接口层(对应OSI物理层,数据链路层)
Internet层(OSI网络层),传输层(同OSI)应用层(OSI会话层、表示层、应用层)

更详细的内容来自百度百科

TCP/IP是一组用于实现网络互连的通信协议。Internet网络体系结构以TCP/IP为核心。基于TCP/IP的参考模型将协议分成四个层次,它们分别是:网络访问层、网际互连层、传输层(主机到主机)、和应用层。   

1.应用层   

应用层对应于OSI参考模型的高层,为用户提供所需要的各种服务,例如:FTP、Telnet、DNS、SMTP等.    

2.传输层   

传输层对应于OSI参考模型的传输层,为应用层实体提供端到端的通信功能。该层定义了两个主要的协议:传输控制协议(TCP)和用户数据报协议(UDP).TCP协议提供的是一种可靠的、面向连接的数据传输服务;而UDP协议提供的则是不可靠的、无连接的数据传输服务.    

3.网际互联层   

网际互联层对应于OSI参考模型的网络层,主要解决主机到主机的通信问题。该层有四个主要协议:网际协议(IP)、地址解析协议(ARP)、互联网组管理协议(IGMP)和互联网控制报文协议(ICMP)。IP协议是网际互联层最重要的协议,它提供的是一个不可靠、无连接的数据报传递服务。   

4.网络访问层   

网络访问层与OSI参考模型中的物理层和数据链路层相对应。事实上,TCP/IP本身并未定义该层的协议,而由参与互连的各网络使用自己的物理层和数据链路层协议,然后与TCP/IP的网络访问层进行连接。

2)用简单语言描述数据库操作步骤:http://210.28.216.200/cai/shujukuxitong/kcjj/chapter01/right1_6.htm

http://www.blogjava.net/youling/archive/2008/11/28/243177.html

到这里,还得说说百度的一面:一面问了我很多问题,总共面了大概1个小时,面试官是个挺年轻的小伙,先上来就问我昨天的一道算法设计题:

1、公司技术部接到一个任务,需要使用a-z,0-9组成3位的字符密码,现请你设计一个算法,将可能的密码组合全部打印出来。

我试卷上是用的递归,主要是时间也比较紧,具体的可以参照这个哥们的,意思意思就行:http://blog.csdn.net/leo115/article/details/8067496。面试的时候那个面试官让我用非递归的方法重写一遍,同时还提醒了我,可以考虑36进制!(0-9、A-F是16进制,0-9、A-Z就是36进制!)

这样一想的话,这道题的复杂度瞬间从O(n^3)降到O(n),因为我们只需把所有的36进制的3位数从小到到打印输出即可!

#include<cstring>
#include<iostream>
void main(){
	const char map[]={'0','1','2','3','4','5','6','7','8','9',
		'A','B','C','D','E','F','G','H','I','J','K','L','M','N',
		'O','P','Q','R','S','T','U','V','W','X','Y','Z'};
	int len=sizeof(map)/sizeof(char);
	int i;
	for(i=0;i<len;i++)
		std::cout<<"00"<<map[i]<<"\t";
	for(i=len;i<len*len;i++)
		std::cout<<"0"<<map[i/len]<<map[i%len]<<"\t";
	for(i=len*len;i<len*len*len;i++)
		std::cout<<map[i/(len*len)]<<map[(i%(len*len))/len]<<map[(i%(len*len))%len]<<"\t";
	system("pause");
return;
}

2、二叉树的非递归后序遍历(当时就写出了递归的算法,非递归愣是没写出像样的来~)
算法思路:先找到最左边的结点,并将路上遇到的结点压栈,然后取栈顶元素(即最左边的结点),1)检查他是否有右子树 2)右子树是否被访问过

如果没有右子树或者右子树已经被访问过,则访问当前结点,然后将pre指向当前结点,并将当前结点从栈中删除(当前结点出栈),否则(有右子树且未被访问过),把右子树压栈。如下是网友fly1988happy写的一个非递归程序:

//二叉树的后序非递归遍历
void PostOrderTraverse(TreeNode *root){
	if(NULL==root) return;
	std::stack<TreeNode*> s;
	TreeNode * pre=NULL;
	while(root!=NULL || !s.empty()){
		while(root!=NULL){
		s.push(root);
		root=root->pLeft;
		}
		if(!s.empty()){
			root=s.top();
			if(NULL==root->pRight || root->pRight==pre)
			{
				visit(root);
				s.pop();
				pre=root;
				root=NULL;
			}
			else 
				root=root->pRight;
	    }

	}
}


除此之外,前序和中序的非递归遍历点这里

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值