百度2011.10.16校园招聘会笔试题

 百度2011.10.16校园招聘会笔试题



一. 算法设计
1. 设rand(s,t)返回[s,t]之间的随机小数,利用该函数在一个半径为R的圆内找随机n个点,并给出时间复杂度分析。
  解: 通过求得点的坐标的方法,代码如下:
void GetNPointsInCircle(int R,int n)
{
  for(int i=0;i<n;i++)
  {
     float x=rand(-R,R);
     float y=rand(-sqrt(R*R-x*x),sqrt(R*R-x*x));
    printf("%f,%f\t",x,y);
  }
}
  也可以通过极坐标的思路:
存放点的数据结构
typedef struct Point{
double r;
double angle;
}Point;
算法流程:
for( i=n ; i >0; i--)
   产生 p.r = rand(0,R),且p.r != R
   产生 p.rangle = rand(0,360) ,且 p.rangle != 360
   遍历所有产生的点,若 p 已经存在,则重新生成该点.
   否则将其加入到产生的点集合中.
  生成第n个点,需要先遍历前n-1个点,时间复杂度为O(n),故生成n个点的时间复杂度为O(n^2).

2. 为分析用户行为,系统常需存储用户的一些query,但因query非常多,故系统不能全存,设系统每天只存m个query,现设计一个算法,对用户请求的query进行随机选择m个,请给一个方案,使得每个query被抽中的概率相等,并分析之,注意:不到最后一刻,并不知用户的总请求量。
解:这其实是一个蓄水池抽样问题.随机抽样问题表示如下: 要求从N个元素中随机的抽取k个元素,其中N无法确定.

开始把前k个元素都放到水库中,然后对之后的第i个元素,以k/i的概率替换掉这个水库中的某一个元素。

【证明】

(1)初始情况。出现在水库中的k个元素的出现概率都是一致的,都是1。这个很显然。

(2)第一步。第一步就是指,处理第k+1个元素的情况。分两种情况:元素全部都没有被替换;其中某个元素与第k+1个元素交换。

我们先看情况2:第k+1个元素被选中的概率是k/(k+1)(根据公式k/i),所以这个新元素在水库中出现的概率就一定是k/(k+1)(不管它替换掉哪个元素,反正肯定它是以这个概率出现在水库中)。下面来看水库中剩余的元素出现的概率,也就是1-P(这个元素被替换掉的概率)。水库中任意一个元素被替换掉的概率是:(k/k+1)*(1/k)=1/(k+1),意即首先要第k+1个元素被选中,然后自己在集合的k个元素中被选中。那它出现的概率就是1-1/(k+1)=k/(k+1)。可以看出来,旧元素和新元素出现的概率是相等的。

情况1:当元素全部都没有替换掉的时候,每个元素的出现概率肯定是一样的,这很显然。但具体是多少呢?就是1-P(第k+1个元素被选中)=1-k/(k+1)=1/(k+1)。

(3)归纳法:重复上面的过程,只要证明第i步到第i+1步,所有元素出现的概率是相等的即可。

3、C++ STL中vector的相关问题:
    (1)、调用push_back时,其内部的内存分配是如何进行的?
    (2)、调用clear时,内部是如何具体实现的?若想将其内存释放,该如何操作?

vector的工作原理是系统预先分配一块CAPACITY大小的空间,当插入的数据超过这个空间的时候,这块空间会让某种方式扩展,但是你删除数据的时候,它却不会缩小。
  vector为了防止大量分配连续内存的开销,保持一块默认的尺寸的内存,clear只是清数据了,未清内存,因为vector的capacity容量未变化,系统维护一个的默认值。

有什么方法可以释放掉vector中占用的全部内存呢?

标准的解决方法如下
template < class T >
void ClearVector( vector< T >& vt )
{
vector< T > vtTemp;
veTemp.swap( vt );
}

  事实上,vector根本就不管内存,它只是负责向内存管理框架acquire/release内存,内存管理框架如果发现内存不够了,就malloc,但是当vector释放资源的时候(比如destruct), stl根本就不调用free以减少内存,因为内存分配在stl的底层:stl假定如果你需要更多的资源就代表你以后也可能需要这么多资源(你的list, hashmap也是用这些内存),所以就没必要不停地malloc/free。如果是这个逻辑的话这可能是个trade-off

  一般的STL内存管理器allocator都是用内存池来管理内存的,所以某个容器申请内存或释放内存都只是影响到内存池的剩余内存量,而不是真的把内存归还给系统。这样做一是为了避免内存碎片,二是提高了内存申请和释放的效率——不用每次都在系统内存里寻找一番。


二. 系统设计
正常用户端每分钟最多发一个请求至服务端,服务端需做一个异常客户端行为的过滤系统,设服务器在某一刻收到客户端A的一个请求,则1分钟内的客户端任何其它请求都需要被过滤,现知每一客户端都有一个IPv6地址可作为其ID,客户端个数太多,以至于无法全部放到单台服务器的内存hash表中,现需简单设计一个系统,使用支持高效的过滤,可使用多台机器,但要求使用的机器越少越好,请将关键的设计和思想用图表和代码表现出来。

解:每个IPv6为128位,一共有2^128个,一个IPV6地址为16字节,总需要容量2^132字节,也就是2^102G.
这个相当大,再多的电脑也不够用的,必须使用数据库和hash方法.
思路:根据IP地址哈希到若干机器上,在内存中hash cache,另外送一份数据到后台数据库.
总体架构就是:
                    前端服务器
                   /     /      \    \   \

    hash cached主机 hash cached主机 hash cached主机 hash cached主机 hash cached主机
                 \         \      /           /        \
                     DB服务器    DB服务器   DB服务器
hash主机用memcached那种系统,另外一个hash主机对一个DB服务器,或者可以多个hash主机对一个DB服务器都行。
IPV6地址16字节,分4部分,一个部分4字节作为UINT。 memcached源码里我记得是有字节流哈希函数的,拿来用也许不错。

三. 求一个全排列函数
如p([1,2,3])输出:
[123]、[132]、[213]、[231]、[321]、[312]
 求一个组合函数。
解:

方法1:依次从字符串中取出一个字符作为最终排列的第一个字符,对剩余字符组成的字符串生成全排列,最终结果为取出的字符和剩余子串全排列的组合。

[cpp]  view plain copy
  1. #include <iostream>  
  2. #include <string>  
  3. using namespace std;  
  4.    
  5. void permute1(string prefix, string str)  
  6. {  
  7.     if(str.length() == 0)  
  8.         cout << prefix << endl;  
  9.     else  
  10.     {  
  11.         for(int i = 0; i < str.length(); i++)  
  12.             permute1(prefix+str[i], str.substr(0,i)+str.substr(i+1,str.length()));  
  13.     }  
  14. }  
  15.    
  16. void permute1(string s)  
  17. {  
  18.     permute1("",s);  
  19. }  
  20.    
  21. int main(void)  
  22. {  
  23.     //method1, unable to remove duplicate permutations.  
  24.     permute1("abc");  
  25.     return 0;  
  26. }  

优点:该方法易于理解,但无法移除重复的排列,如:s="ABA",会生成两个“AAB”。

方法2:利用交换的思想,具体见实例,但该方法不如方法1容易理解。

我们以三个字符abc为例来分析一下求字符串排列的过程。首先我们固定第一个字符a,求后面两个字符bc的排列。当两个字符bc的排列求好之后,我们把第一个字符a和后面的b交换,得到bac,接着我们固定第一个字符b,求后面两个字符ac的排列。现在是把c放到第一位置的时候了。记住前面我们已经把原先的第一个字符a和后面的b做了交换,为了保证这次c仍然是和原先处在第一位置的a交换,我们在拿c和第一个字符交换之前,先要把b和a交换回来。在交换b和a之后,再拿c和处在第一位置的a进行交换,得到cba。我们再次固定第一个字符c,求后面两个字符b、a的排列。

既然我们已经知道怎么求三个字符的排列,那么固定第一个字符之后求后面两个字符的排列,就是典型的递归思路了。

基于前面的分析,我们可以得到如下的参考代码:

[cpp]  view plain copy
  1. void Permutation(char* pStr, char* pBegin)  
  2. {  
  3.     assert(pStr && pBegin);  
  4.     //if(!pStr || !pBegin)  
  5.         //return ;  
  6.     if(*pBegin == '\0')  
  7.         printf("%s\n",pStr);  
  8.     else  
  9.     {  
  10.         char temp;  
  11.         for(char* pCh = pBegin; *pCh != '\0'; pCh++)  
  12.         {  
  13.             if(pCh != pBegin && *pCh == *pBegin)   //为避免生成重复排列,当不同位置的字符相同时不再交换  
  14.                 continue;  
  15.             temp = *pCh;  
  16.             *pCh = *pBegin;  
  17.             *pBegin = temp;  
  18.   
  19.             Permutation(pStr, pBegin+1);  
  20.   
  21.             temp = *pCh;  
  22.             *pCh = *pBegin;  
  23.             *pBegin = temp;  
  24.         }  
  25.     }  
  26. }  
  27.   
  28. int main(void)  
  29. {  
  30.     char str[] = "aba";  
  31.     Permutation(str,str);  
  32.     return 0;  
  33. }  

如p([1,2,3])输出:
[1]、[2]、[3]、[1,2]、[2,3]、[1,3]、[1,2,3]
这两问可以用伪代码。



参考资料:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值