十月份第二週筆試題

這一週是國慶長假結束之後的一週,先後參加了宇龍酷派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;
	    }

	}
}


除此之外,前序和中序的非遞歸遍歷點這裏

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章