腾讯2013校园招聘笔试题

来源:互联网 发布:海知智能 王宇 编辑:程序博客网 时间:2024/05/19 22:46

一、简答题

1.简述数据库以及线程死锁产生的原理及必要条件,简述如何避免死锁。

数据可以及线程死锁产生的原理:进程P1(事务)占用资源R1时,进程P2(事务)占用资源R2时;P1下一步要用R2,P2要用R1;此时,两者均得不到所申请的资源而无法继续向前推进,这种由于竞争资源而引起的僵持称为死锁。

产生死锁的必要条件:

  • 资源独占:一个资源同时只能分配给一个进程(事务)。

  • 不可剥夺:资源申请者不能强行从资源占有者手中夺取资源,既资源只能有占有者使用完后自愿释放。

  • 保持申请 :  进程占有部分资源后还可以申请新的资源,而且在申请新的资源的时候并不释放它已经占有的资源。

  • 循环等待:存在一个进程等待序列{P1,P2,.......,Pn},其中P1等待P2,P2等待P3,.......,Pn等待P1.

避免死锁的方法:就是破坏上面4个必要条件中的任一个。具体方法有:

                           一次封锁法:每个进程(事务)将所有要使用的数据全部加锁,否则就不能继续执行。

                           顺序封锁法:预先对数据对象规定一个封锁顺序,所有进程(事务)都按照这个顺序加锁。

                                      等待图法:如果出现了回路,则表示系统中出现了死锁;

                           超时法:如果一个进程(事务)的等待时间超过了规定的时间,则认为发生了死锁。

                           银行家算法:保证进程处于安全进程序列;

2.请列举面向对象设计的三个基本要素和五种主要设计原则。

三个基本要素:封装、继承、多态。

五种设计原则:

  •     单一职责原则:一个类只有一种功能。

  •     开放封闭原则:软件实体是可扩展的而是不可修改的。

  •    里氏替换原则:子类必须能够替换基类。

  •    依赖倒置原则:依赖于抽象。

  •    接口隔离原则:使用多个小的专门的接口,而不要使用一个大的接口。

3.简述windows内存管理的几种方式及优缺点。

  • 单对界地址存储管理(单一连续区域存储管理):

              a.内存空间划分 : 内存空间采用动态异常分区方法,整个内存被动态的划分为若干个长度各异的区域。

             b.进程空间划分 : 一个进程空间有连续的区域构成,假设进程的长度为l,则其逻辑地址为0到l-1.

             c.进程空进与内存空间的对应关系:一个进程在内存中占有一个连续的区域。假设进程的长度为l,在内存中的起始地址为b,则其物理地址为b至b+l-1。

             d.特点:要求一个进程占用内存空间的一个连续区域,因而利用动态异长存储分配的方法可能会产生碎片。

  • 页式存储管理:

            a.内存空间划分 : 内存空间被静态的划分为若干个等长的区域。每个区域被称为一个物理页框。

                   b.进程空间划分 : 进程空间也被静态的划分为若干个等长的区域,每个区域称为一个逻辑页面,其长度与页框的长度相等。

             c.进程空进与内存空间的对应关系:当进程运行时,需要将它的各个逻辑页面保存到存储空间的物理页框中,即需要确定逻辑页面与页框的对应关系,进程的逻辑页面是连续的,但是页框页面却不一定是连续的。

             d.特点:允许一个进程占用内存空间中多个连续的区域,而这些区域的长度相等,因而采用静态等长存储分配的方法,不会产生碎片。

  • 段式存储管理:

           a.内存空间划分 : 内存空间被动态的划分为若干个长度各异的区域,每个区域称为一个物理段。

                  b.进程空间划分 : 进程空间被静态的划分为若干个长度各异的区域,每个区域称为一个逻辑段。一个逻辑段通常对应一个程序段,各个程序断的长度使不等的。

            c.进程空进与内存空间的对应关系:进程的一个逻辑段与内存的一个物理段相对应。一个进程的多个逻辑段可存放在内存的若干个不相连的物理段中。

            d.特点:会产生碎片,但是便于实现共享。

  • 断页式存储管理:

                 a.内存空间划分 : 与页式存储管理相同。

            b.进程空间划分 :与段式存储管理相同。

            c.进程空进与内存空间的对应关系:进程空间的一个逻辑页面对应内存空间的一个页框。同一段内的逻辑地址是连续的,而对应的页框却未必是连续。

            d.特点:既不会产生碎片,又便于共享。

二、算法和程序设计

      1、公司组织一次羽毛球比赛,采用淘汰机制,假设公司有1001个人,如果要评出“公司羽毛球第一高手”的称号,至少需要进行多少场比赛?请简述设计过程,并写出代码模拟比赛过程。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<iostream> 
using namespace std;
int main()
{
 int n=1001;//一共的人数
 int num=0;//比赛的场数
 int remain=0;//剩余的人数
for(n=1001;n>1;n=n/2)
 {
  if(n%2!=0)
   if(remain==0)remain=1;
   else{remain=0;n++;}
  num+=n/2;
 }
 cout<<num<<endl;
 return 0;
}


      2、一百个灯泡排成一排,第一轮将所有灯泡打开;第二轮每隔一个灯泡关掉一个。即排在偶数的灯泡被关掉,第三轮每隔两个灯泡,将开着的灯泡关掉,关掉的灯泡打开。依次类推,第100轮结束的时候,还有几盏灯泡亮着。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<iostream>
 
#define NUM 100
 
using namespace std;
 
int main()
 
{   
 
 int a[NUM];
 
 int count=0;
 
 for(int i=0;i<NUM;i++)//把所有的灯打开
 
  a[i]=1;
 
 for(i=0;i<NUM;i++)
 
  for(int j=2;j<NUM;j++)
 
   if(i%j==0)
 
    if(a[i]==1)a[i]=0;
 
    else a[i]=1;
 
 for(i=0;i<NUM;i++)//统计开着的个数
 
  if(a[i]==1)
 
   count++;
 
 cout<<count<<endl;
 
 return 0;
 
}


      3、假定有20个有序数组,每个数组有500个数字,数字类型32位uint数值,现在需要取出这10000个数字中最大的500个,怎么做?

 思路:20个有序数组,不妨设数序为降序。首先把20个数组中最大的(也就是a[i][0])进行大堆排序,输出堆顶元素(最大),在输出的那个元素之前所在的数组中,取下一个元素,放到堆顶,然后进行堆排序,在输出堆顶元素,依次循环输出500个即可。(输出最小的500,同样的道理,只不过进行小堆排序),这样充分利用了题干的条件。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include<iostream>
using namespace std;
struct node
{
int data;
int next;
};
node obj[20];
void sift(int k,int m,int b[])//调整一个元素在堆的位置
{
     int i=k,j=2*i+1,temp;
     while(j<m)
  {
       if(j<m-2&&b[j]<b[j+1])// 体会j<m
           j++;
        if(b[i]>=b[j]) break;
       else {
        temp=b[i];
        b[i]=b[j];
        b[j]=temp;
        i=j;
        j=2*j+1;
    }
  }
}
void s_hsort(int n,int a[][500])
{
    int temp=0,j=0;
 int b[20];
 for(int k=0;k<20;k++)
 {
  b[k]=a[k][0];
  obj[k].data=a[k][0];
  obj[k].next=1;
 }
  for(k=0;k<500;k++)
 {
    for(int i=(n-1)/2;i>=0;i--)//把20个元素调整为大堆的形式
       sift(i,n,b);
    cout<<b[0]<<endl;//输出堆顶
  for(i=0;i<20;i++)//把堆顶值换成下一个入堆的元素
    if(b[0]==obj[i].data)
        temp=i;
   b[0]=a[temp][obj[temp].next];
   obj[temp].data=b[0];
   obj[temp].next+=1;
}
int main()
{
 int a[20][500];
for(int j=0;j<20;j++)//初始化这个二维数组
  for(int i=0;i<500;i++)
  a[j][i]=10000-(i+1)-50*j;
  
s_hsort(20,a);
return 0;
}


      4、字符串左移,void *pszStringRotate(char *pszString, intnCharsRotate),比如ABCDEFG,移3位变DEFGABC,要求空间复杂度O(1),时间复杂度O(n)。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<iostream>
 
using namespace std;
 
void reverse(int m,int n,char a[])//实现反转
 
{
 
   char temp;
 
   for(int i=m,j=n;i<=(m+n)/2;i++,j--)
 
   {
 
 temp=a[i];
 
    a[i]=a[j];
 
    a[j]=temp;
 
   }
 
}
 
int main()
 
{
 
 char a[]="ABCDEFG";
 
 reverse(0,2,a);//三次反转产生循环移位的效果,完全符合题意
 
 reverse(3,6,a);
 
 reverse(0,6,a);
 
 cout<<a<<endl;
 
 return 0;
 
}


三、系统设计题

      手机上通常采用九键键盘输入。即:1-9个数字分别对应一定的英文字母(如:2对应ABC, 3对应DEF,...),因此,用户可以方便的输入中文内容。比如,用户输入“926”,可以对应“WXYZ”,“ABC"和”MNO“的一系列组合”WAN”,“YAN"、”ZAO“等,这些对应“万”,“严”,“早”等汉字的中文拼音。

      要求我们把这样的输入方式应用在我们的手机联系人查找功能上。有一个联系人列表UserList,记录了(姓名,手机号)这样的组合,通过输入的数字字符串NumStr,按照下面的规则把对应的联系人查找出来,返回一个ReaultList。

      规则:

      1、手机号能连续部分匹配输入的数字字符串NumStr。如输入NumStr=926,则手机号为13926811111会被查出来;

      2、联系人姓名中的汉字转化成拼音后能够连续匹配输入数字字符串NumStr对应的英文字母组合,如:输入NumStr=926,则联系人“王二”、“万事通”会被查找出来。因为“王二”的“王”的拼音“WANG”中含有“WAN”,和“926”能匹配。

      输入:联系人列表UserList<UserName, PhoneNo>;汉字拼音射射表Dict,数字拼音字符串NumStr。

      输出:符合规则的联系人列表ResultList<UserName, PhoneNo>。

思路:根据numstr进行字符匹配,之间在phoneno中进行就可。把numstr所对应的字母:如926,可以组成4*3*3中组合,把相应的字符串存起来,。

        联系人列表UserList<UserName, PhoneNo>中可以把姓氏按拼音存到一个数据表中,并且按照自己定义的一个映射表,得到姓氏所对应的数字串,然后在进行匹配。

一、简答题

1.简述数据库以及线程死锁产生的原理及必要条件,简述如何避免死锁。

数据可以及线程死锁产生的原理:进程P1(事务)占用资源R1时,进程P2(事务)占用资源R2时;P1下一步要用R2,P2要用R1;此时,两者均得不到所申请的资源而无法继续向前推进,这种由于竞争资源而引起的僵持称为死锁。

产生死锁的必要条件:

  • 资源独占:一个资源同时只能分配给一个进程(事务)。

  • 不可剥夺:资源申请者不能强行从资源占有者手中夺取资源,既资源只能有占有者使用完后自愿释放。

  • 保持申请 :  进程占有部分资源后还可以申请新的资源,而且在申请新的资源的时候并不释放它已经占有的资源。

  • 循环等待:存在一个进程等待序列{P1,P2,.......,Pn},其中P1等待P2,P2等待P3,.......,Pn等待P1.

避免死锁的方法:就是破坏上面4个必要条件中的任一个。具体方法有:

                           一次封锁法:每个进程(事务)将所有要使用的数据全部加锁,否则就不能继续执行。

                           顺序封锁法:预先对数据对象规定一个封锁顺序,所有进程(事务)都按照这个顺序加锁。

                                      等待图法:如果出现了回路,则表示系统中出现了死锁;

                           超时法:如果一个进程(事务)的等待时间超过了规定的时间,则认为发生了死锁。

                           银行家算法:保证进程处于安全进程序列;

2.请列举面向对象设计的三个基本要素和五种主要设计原则。

三个基本要素:封装、继承、多态。

五种设计原则:

  •     单一职责原则:一个类只有一种功能。

  •     开放封闭原则:软件实体是可扩展的而是不可修改的。

  •    里氏替换原则:子类必须能够替换基类。

  •    依赖倒置原则:依赖于抽象。

  •    接口隔离原则:使用多个小的专门的接口,而不要使用一个大的接口。

3.简述windows内存管理的几种方式及优缺点。

  • 单对界地址存储管理(单一连续区域存储管理):

              a.内存空间划分 : 内存空间采用动态异常分区方法,整个内存被动态的划分为若干个长度各异的区域。

             b.进程空间划分 : 一个进程空间有连续的区域构成,假设进程的长度为l,则其逻辑地址为0到l-1.

             c.进程空进与内存空间的对应关系:一个进程在内存中占有一个连续的区域。假设进程的长度为l,在内存中的起始地址为b,则其物理地址为b至b+l-1。

             d.特点:要求一个进程占用内存空间的一个连续区域,因而利用动态异长存储分配的方法可能会产生碎片。

  • 页式存储管理:

            a.内存空间划分 : 内存空间被静态的划分为若干个等长的区域。每个区域被称为一个物理页框。

                   b.进程空间划分 : 进程空间也被静态的划分为若干个等长的区域,每个区域称为一个逻辑页面,其长度与页框的长度相等。

             c.进程空进与内存空间的对应关系:当进程运行时,需要将它的各个逻辑页面保存到存储空间的物理页框中,即需要确定逻辑页面与页框的对应关系,进程的逻辑页面是连续的,但是页框页面却不一定是连续的。

             d.特点:允许一个进程占用内存空间中多个连续的区域,而这些区域的长度相等,因而采用静态等长存储分配的方法,不会产生碎片。

  • 段式存储管理:

           a.内存空间划分 : 内存空间被动态的划分为若干个长度各异的区域,每个区域称为一个物理段。

                  b.进程空间划分 : 进程空间被静态的划分为若干个长度各异的区域,每个区域称为一个逻辑段。一个逻辑段通常对应一个程序段,各个程序断的长度使不等的。

            c.进程空进与内存空间的对应关系:进程的一个逻辑段与内存的一个物理段相对应。一个进程的多个逻辑段可存放在内存的若干个不相连的物理段中。

            d.特点:会产生碎片,但是便于实现共享。

  • 断页式存储管理:

                 a.内存空间划分 : 与页式存储管理相同。

            b.进程空间划分 :与段式存储管理相同。

            c.进程空进与内存空间的对应关系:进程空间的一个逻辑页面对应内存空间的一个页框。同一段内的逻辑地址是连续的,而对应的页框却未必是连续。

            d.特点:既不会产生碎片,又便于共享。

二、算法和程序设计

      1、公司组织一次羽毛球比赛,采用淘汰机制,假设公司有1001个人,如果要评出“公司羽毛球第一高手”的称号,至少需要进行多少场比赛?请简述设计过程,并写出代码模拟比赛过程。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<iostream> 
using namespace std;
int main()
{
 int n=1001;//一共的人数
 int num=0;//比赛的场数
 int remain=0;//剩余的人数
for(n=1001;n>1;n=n/2)
 {
  if(n%2!=0)
   if(remain==0)remain=1;
   else{remain=0;n++;}
  num+=n/2;
 }
 cout<<num<<endl;
 return 0;
}


      2、一百个灯泡排成一排,第一轮将所有灯泡打开;第二轮每隔一个灯泡关掉一个。即排在偶数的灯泡被关掉,第三轮每隔两个灯泡,将开着的灯泡关掉,关掉的灯泡打开。依次类推,第100轮结束的时候,还有几盏灯泡亮着。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<iostream>
 
#define NUM 100
 
using namespace std;
 
int main()
 
{   
 
 int a[NUM];
 
 int count=0;
 
 for(int i=0;i<NUM;i++)//把所有的灯打开
 
  a[i]=1;
 
 for(i=0;i<NUM;i++)
 
  for(int j=2;j<NUM;j++)
 
   if(i%j==0)
 
    if(a[i]==1)a[i]=0;
 
    else a[i]=1;
 
 for(i=0;i<NUM;i++)//统计开着的个数
 
  if(a[i]==1)
 
   count++;
 
 cout<<count<<endl;
 
 return 0;
 
}


      3、假定有20个有序数组,每个数组有500个数字,数字类型32位uint数值,现在需要取出这10000个数字中最大的500个,怎么做?

 思路:20个有序数组,不妨设数序为降序。首先把20个数组中最大的(也就是a[i][0])进行大堆排序,输出堆顶元素(最大),在输出的那个元素之前所在的数组中,取下一个元素,放到堆顶,然后进行堆排序,在输出堆顶元素,依次循环输出500个即可。(输出最小的500,同样的道理,只不过进行小堆排序),这样充分利用了题干的条件。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include<iostream>
using namespace std;
struct node
{
int data;
int next;
};
node obj[20];
void sift(int k,int m,int b[])//调整一个元素在堆的位置
{
     int i=k,j=2*i+1,temp;
     while(j<m)
  {
       if(j<m-2&&b[j]<b[j+1])// 体会j<m
           j++;
        if(b[i]>=b[j]) break;
       else {
        temp=b[i];
        b[i]=b[j];
        b[j]=temp;
        i=j;
        j=2*j+1;
    }
  }
}
void s_hsort(int n,int a[][500])
{
    int temp=0,j=0;
 int b[20];
 for(int k=0;k<20;k++)
 {
  b[k]=a[k][0];
  obj[k].data=a[k][0];
  obj[k].next=1;
 }
  for(k=0;k<500;k++)
 {
    for(int i=(n-1)/2;i>=0;i--)//把20个元素调整为大堆的形式
       sift(i,n,b);
    cout<<b[0]<<endl;//输出堆顶
  for(i=0;i<20;i++)//把堆顶值换成下一个入堆的元素
    if(b[0]==obj[i].data)
        temp=i;
   b[0]=a[temp][obj[temp].next];
   obj[temp].data=b[0];
   obj[temp].next+=1;
}
int main()
{
 int a[20][500];
for(int j=0;j<20;j++)//初始化这个二维数组
  for(int i=0;i<500;i++)
  a[j][i]=10000-(i+1)-50*j;
  
s_hsort(20,a);
return 0;
}


      4、字符串左移,void *pszStringRotate(char *pszString, intnCharsRotate),比如ABCDEFG,移3位变DEFGABC,要求空间复杂度O(1),时间复杂度O(n)。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<iostream>
 
using namespace std;
 
void reverse(int m,int n,char a[])//实现反转
 
{
 
   char temp;
 
   for(int i=m,j=n;i<=(m+n)/2;i++,j--)
 
   {
 
 temp=a[i];
 
    a[i]=a[j];
 
    a[j]=temp;
 
   }
 
}
 
int main()
 
{
 
 char a[]="ABCDEFG";
 
 reverse(0,2,a);//三次反转产生循环移位的效果,完全符合题意
 
 reverse(3,6,a);
 
 reverse(0,6,a);
 
 cout<<a<<endl;
 
 return 0;
 
}


三、系统设计题

      手机上通常采用九键键盘输入。即:1-9个数字分别对应一定的英文字母(如:2对应ABC, 3对应DEF,...),因此,用户可以方便的输入中文内容。比如,用户输入“926”,可以对应“WXYZ”,“ABC"和”MNO“的一系列组合”WAN”,“YAN"、”ZAO“等,这些对应“万”,“严”,“早”等汉字的中文拼音。

      要求我们把这样的输入方式应用在我们的手机联系人查找功能上。有一个联系人列表UserList,记录了(姓名,手机号)这样的组合,通过输入的数字字符串NumStr,按照下面的规则把对应的联系人查找出来,返回一个ReaultList。

      规则:

      1、手机号能连续部分匹配输入的数字字符串NumStr。如输入NumStr=926,则手机号为13926811111会被查出来;

      2、联系人姓名中的汉字转化成拼音后能够连续匹配输入数字字符串NumStr对应的英文字母组合,如:输入NumStr=926,则联系人“王二”、“万事通”会被查找出来。因为“王二”的“王”的拼音“WANG”中含有“WAN”,和“926”能匹配。

      输入:联系人列表UserList<UserName, PhoneNo>;汉字拼音射射表Dict,数字拼音字符串NumStr。

      输出:符合规则的联系人列表ResultList<UserName, PhoneNo>。

思路:根据numstr进行字符匹配,之间在phoneno中进行就可。把numstr所对应的字母:如926,可以组成4*3*3中组合,把相应的字符串存起来,。

        联系人列表UserList<UserName, PhoneNo>中可以把姓氏按拼音存到一个数据表中,并且按照自己定义的一个映射表,得到姓氏所对应的数字串,然后在进行匹配。

0 0