华三面试题一

华三最新笔试题 2012 6 月华三笔试题

一、不定项选择题(凭印象写的,没有顺序)(每题5分,共65分,13题):

1、define  N  2

     define  NTI(n)  N*n+n

   Sum=N*NTI(N);

   问Sum为多少?

答:

Sum = N*NTI(N)=N*N*N+N = 2*2*2+2=10

 

2、以下几个哪个优先级最高?

A.%           B.<<           C.忘了          D.<<=

B. <<最高

3、一个12人玩的游戏需要正好75分钟,有六个后备人员轮流替换从开始就参加玩游戏的人,也就是说所有参加游戏的人,包括后备人员,在这个游戏中都玩了同样长的时间。他们每人玩了多长时间?

A. 30分钟  B. 40分钟  C. 50分钟

D. 55分钟  E. 60分钟

答:游戏总耗时 12*75,总共18个人,那么每个人:900/18=50,选择C

4、在跑马场的跑道上,有A B C 三匹马,A在一分钟内能跑两圈,B能跑三圈,C能跑四圈。现将三匹马并排在起跑线上,准备向同一个方向起跑。请问:经过几分钟,这三匹马又能并排地跑在起跑线上了?(选项忘了)

答:A每圈30秒,B每圈20秒,C每圈15秒,公倍数60秒,一分钟

5、61个球队,两两比赛,胜者晋级,败者淘汰。如果淘汰后的队伍数是单数,则首支队伍轮空直接晋级,剩下的队伍继续进行淘汰赛,直至选出最后胜者为止,问一共需进行几场比赛?(选项忘了)

答:(1)一支队伍轮空,直接晋级,30场比赛

(2)31个队伍,一支队伍轮空,15场比赛,16强

(3)16进8 8场

(4)8进4 4场

(5)4进2 2场

(6)2进1 1场

故:30+15+8+4+2+1=60场

6、有关霍夫曼树和平衡树的选择题(定义什么的去看看,有个选项说霍夫曼树可应用于编码)

复习下哈夫曼树和平衡树

7、有一题关于获取mac地址的函数(题目给出一个函数的实现代码),考的是字符串的东西,还有sprintf函数

(下面是函数中运用sprintf函数的代码:    sprintf(mac,"%02X-%02X-%02X-%02X-%02X-%02X",
         Adapter.adapt.adapter_address[0],
         Adapter.adapt.adapter_address[1],
         Adapter.adapt.adapter_address[2],
         Adapter.adapt.adapter_address[3],
         Adapter.adapt.adapter_address[4],
         Adapter.adapt.adapter_address[5]);

有几行有标记,问你标记的这几行中哪几行有问题或隐患,给出选项要你选,是道多选题。

如果遇到这个临场发挥吧,不知道题目是什么。

   8、还有一题找图形规律,很简单。

   目前只能回忆这么多。

二、编程题:

1、给一个结构体(15)

Struct NODE_S{

int  data;

NODE_S  *next;

NODE_S  *preptr;

}

给一个头指针指向该结构体的节点的链表,写一函数判断该链表是否是双向循环链表。

答:

(1)判断是不是循环量表

设置两个指针(fast, slow),初始值都指向头,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。(当然,fast先行头到尾部为NULL,则为无环链表)程序如下:

bool IsExitsLoop(slist *head)
{
    slist *slow = head, *fast = head;

    while ( fast && fast->next ) 
    {
        slow = slow->next;
        fast = fast->next->next;
        if ( slow == fast ) break;
    }

    return !(fast == NULL || fast->next == NULL);
}

 

(2)再反向遍历,如果也是循环链表,就是双向循环链表

 

2、给一个字符串,写一个函数返回该字符串的最长回文子串的长度,无回文子串则返回0。(20分)

 

参考如下文章:

 http://www.cnblogs.com/houkai/p/3371807.html

回文串就是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串。回文子串,顾名思义,即字符串中满足回文性质的子串。比如输入字符串 "google”,由于该字符串里最长的对称子字符串是 "goog”,因此输出4

1.问题解决的基本方法

分析:可能很多人都写过判断一个字符串是不是对称的函数,这个题目可以看成是该函数的加强版。 
要判断一个字符串是不是对称的,不是一件很难的事情。我们可以先得到字符串首尾两个字符,判断是不是相等。如果不相等,那该字符串肯定不是对称的。否则我们接着判断里面的两个字符是不是相等,以此类推。

<pre name="code" class="cpp">/*
 *作者:侯凯
 *说明:求最长回文字符串
 *日期:2013-10-15
*/
#include<iostream>
using namespace std;
//字符串是否对称
bool isAym(char *cbegin, char *cend)
{
    if(cbegin == NULL || cend ==NULL || cbegin > cend)
    {
        return false;
    }
    while(cbegin<cend)
    {
        if(*cbegin!=*cend)
        {
            return false;
        }
        cbegin++;
        cend--;
    }
    return true;
}


 
 

现在我们试着来得到对称子字符串的最大长度。最直观的做法就是得到输入字符串的所有子字符串,并逐个判断是不是对称的。如果一个子字符串是对称的,我们就得到它的长度,最后经过比较,就能得到最长的对称子字符串的长度了。

//O(n*n*n)复杂度的子字符串
int getMaxSym(char * str)
{
    if(str == NULL)
        return 0;
    int maxlength = 0, strlength = 0;
    char *pFirst = str;
    char *strEnd = str + strlen(str);
    while(pFirst < strEnd)
    {
        char *pLast = strEnd;
        while(pLast > pFirst)
        {
            if(isAym(pFirst, pLast))
            {
                strlength = pLast - pFirst + 1;
                if(strlength > maxlength)
                {
                    maxlength = strlength;
                }
            }
            pLast --;
        }
        pFirst ++;
    }
    return maxlength;
}


上述方法的时间效率:由于需要两重while循环,每重循环需要On)的时间。另外,我们在循环中调用了IsSym,每次调用也需要On)的时间。因此整个函数的时间效率是On^3)。 
假设输入:abcddcba,按照上述程序,要分割成 'abcddcba’, 'bcddcb’, 'cddc’, 'dd’…等字符串,并对这些字符串分别进行判断。不难发现,很多短子字符串在长些的子字符串中比较过,这导致了大量的冗余判断,根本原因是:对字符串对称的判断是由外向里进行的。 
换一种思路,从里向外来判断。也就是先判断子字符串(dd)是不是对称的。如果它(dd)不是对称的,那么向该子字符串两端各延长一个字符得到的字符串肯定不是对称的。如果它(dd)对称,那么只需要判断它(dd)两端延长的一个字符是不是相等的,如果相等,则延长后的字符串是对称的。

2.改进的解决方案

根据从里向外比较的思路写出如下代码:

//改进后的程序
int getMaxSym2(char * str)
{
    if(str == NULL)
        return 0;
    int maxlength = 0;
    char *ptag = str;
    while(*ptag !='\0')
    {
        //奇数子字符串
         char *left = ptag - 1;
        char *right = ptag + 1;
        int oddlenght = 1;
        while(left >= str && *right != '\0' && *left == *right)
        {
            left--;
            right++;
            oddlenght += 2;
        }
        if(oddlenght > maxlength)
        {
            maxlength = oddlenght;
        }
        //偶数子字符串
         left = ptag;
        right = ptag + 1;
        int evenlength = 0;
        while(left >= str && *right != '\0' && *left == *right)
        {
            left--;
            right++;
            evenlength += 2;
        }
        if(evenlength > maxlength)
        {
            maxlength = evenlength;
        }

        ptag++;
    }
    return maxlength;
}


由于子字符串的长度可能是奇数也可能是偶数。长度是奇数的字符串是从只有一个字符的中心向两端延长出来,而长度为偶数的字符串是从一个有两个字符的中心向两端延长出来。因此程序中要把这两种情况都考虑进去。 
由于总共有On)个字符,每个字符可能延长On)次,每次延长时只需要O1)就能判断出是不是对称的,因此整个函数的时间效率是On^2)。 
上述方法称为朴素算法,关于字符串的题目常用的算法有KMP、后缀数组、AC自动机,这道题目利用扩展KMP可以解答,其时间复杂度也很快O(N*logN)。但是,这里介绍一个专门针对回文子串的算法,其时间复杂度为O(n),这就是manacher算法。

3.manacher算法

算法的基本思路是这样的:把原串每个字符中间用一个串中没出现过的字符分隔#开来(统一奇偶),同时为了防止越界,在字符串的首部也加入一个特殊符,但是与分隔符不同。同时字符串的末尾也加入‘\0’。算法的核心:用辅助数组p记录以每个字符为核心的最长回文字符串半径。也就是p[i]记录了以str[i]为中心的最长回文字符串半径。P[i]最小为1,此时回文字符串就是字符串本身。示例:原字符串’abba’,处理后的新串’#a#b#b#a#\0’,得到对应的辅助数组p=[0,1,1,2,1,2,5,2,2,1] 
程序如下,对应的变量解释在后面

//预处理,将str:abba转换为: $#a#b#b#a#\0(从1开始)
char * pre(char *str)
{
    int length = strlen(str);
    char *prestr = new char[2*length + 4];
    prestr[1] = '$';
    for(int i=0;i<length;i++)
    {
        prestr[2*(i+1)] = '#';
        prestr[2*(i+1)+1] = str[i];
    }
    prestr[2*length+2]='#';
    prestr[2*length+3]='\0';
    return prestr;
}


以下是manacher算法的具体实现,包括:辅助数组的构建、最大字符串长度的获取。

//manacher算法
int getMaxSym3(char *str)
{
    char *prestr = pre(str);
    int mx =0, pi=1;//边界和对称中心
    int len = strlen(prestr);
    //辅助数组
    int *p = new int[len];
    p[0] = 0;
    for(int i=1;i<len;i++)
    {
        if(mx>i)
        {
            p[i]=min(mx-i,p[2*pi-i]);//核心
        }
        else
        {
            p[i]=1;
        }
        while(prestr[i-p[i]]==prestr[i+p[i]]&&i-p[i]>0&&i+p[i]<len)
        {
            p[i]++;
        }
        if(i+p[i] > mx)
        {
            mx = p[i] + i;
            pi = i;
        }
    }
    //最大回文字符串长度
    int maxlen = 0;
    for(int i=0;i<len;i++)
    {
        if(p[i]>maxlen)
        {
            maxlen = p[i];
        }
    }
    delete []prestr;
    delete []p;
    return maxlen - 1;
}


上面几个变量说明:pi记录具有遍历过程中最长半径的回文字符串中心字符串。mx记录了具有最长回文字符串的右边界。 
 
pi是最长回文字符串(淡蓝色)的中心,如果以j为中心的最大回文串如上如所示,那么i处的情况与j处相同(关于pi的两侧是对称的)。这样便减少了运算量,i的对称位置是2*pi-i 
但是有另外一种情况,就是j的一部分超出蓝色部分,这时p[i]=p[j]就不一定对了,如下图 
 
这就为什么有取最小值这个操作:

if(mx>i){        p[i]=min(mx-i,p[2*pi-i]);//核心}

剩下的代码就很容易看懂了。

最后遍历一边p数组,找出最大的p[i]-1就是所求的最长回文字符串长度,说明如下:
1)因为p[i]记录插入分隔符之后的回文字符串半径,所以以i为中心的回文字符串长度为2*p[i]-1。例如:bb=>#b#b#,中间#的半径为3,回文字符串长度为2*3-1 
2)注意上面两个串的关系。 #b#b#减去一个#号的长度就是原来的2倍。即((2*p[i]-1)-1)/2 = p[i]-1,得证。

 

和如下文章:

http://blog.163.com/kevinlee_2010/blog/static/169820820201111210740329/

 

问题描述:如题,给定一个字符串str和其长度n,求该字符串的一个最长公共回文子串的长度(公共子串个公共子序列是两个不同的概念)。并打印出该回文子串。

解答:1,首先给出一个比较直观的解法。根据回文的性质,我们可以把str进行逆转得到str1,然后求strstr1的最长公共子串,那么该子串的长度就是str的最长回文子串的长度,该公共子串就是最长的那个回文子串。也即我们把这个题目转化为求两个字符串strstr1的最长公共子串的问题。我们假设C[i,j]为以str[i]str1[j]结尾的最长公共子串的长度,那么状态转移方程为:

C[i,j]=0 if(str[i]!=str1[j])

C[i,j]=c[i-1,j-1]+1  if(str[i]==str1[j])

初始条件为C[0,0]=0;最初的时间代价为O(n^2),空间代价为O(n^2),空间代价还可以优化为O(n),只需要j的循环式从高往低即可。代码如下:

//str1str2的最长公共子串,下标从1算起

void LCS_continue(char *str1,int n1,char *str2,int n2){

int **C=new int*[n1+1];

for(int i=0;i<=n1;i++){

C[i]=new int[n2+1]();

}

int max=0;

int max_index_i=0;

int max_index_j=0;

for(int i=1;i<=n1;i++){

for(int j=1;j<=n2;j++){

if(str1[i]==str2[j])

C[i][j]=C[i-1][j-1]+1;

else

C[i][j]=0;

if(C[i][j]>max){

max=C[i][j];

max_index_i=i;

max_index_j=j;

}

}

}

cout<<"最长公共子串长度为:"<<max<<ends;

if(max>0){

cout<<"子串为:"<<ends;

while(str1[max_index_i]==str2[max_index_j--]){

cout<<str1[max_index_i--]<<ends;

}

}

//free mem

for(int i=0;i<=n1;i++){

delete [] C[i];

}

delete [] C;

}

空间优化:

void LCS_continue_OPM(char *str1,int n1,char *str2,int n2){

int *C=new int[n2+1]();

int max=0;

int max_index_j=0;

for(int i=1;i<=n1;i++){

for(int j=n2;j>0;j--){

if(str1[i]==str2[j])

C[j]=C[j-1]+1;

else

C[j]=0;

if(C[j]>max){

max=C[j];

max_index_j=j;

}

}

}

cout<<"最长公共子串长度为:"<<max<<ends;

if(max>0){

cout<<"子串为:"<<ends;

while(max>0){

cout<<str2[max_index_j--]<<ends;

max--;

}

}

//free mem

delete [] C;

}

2,下面给出一个直观的动规解,

以下内容转自:http://blog.163.com/zhaohai_1988/blog/static/2095100852012716105847112/

方法二 动态规划 时间复杂度O(N2), 空间复杂度O(N2)

    动态规划就是暴力法的进化版本,我们没有必要对每一个子串都重新计算,看看它是不是回文。我们可以记录一些我们需要的东西,就可以在O(1)的时间判断出该子串是不是一个回文。这样就比暴力法节省了O(N)的时间复杂度哦,嘿嘿,其实优化很简单吧。

P(i,j)为1时代表字符串Si到Sj是一个回文,为0时代表字符串Si到Sj不是一个回文。

P(i,j)= P(i+1,j-1)(如果S[i] = S[j])。这是动态规划的状态转移方程。

P(i,i)= 1,P(i,i+1)= if(S[i]= S[i+1])

string longestPalindromeDP(string s) {

  int n = s.length();

  int longestBegin = 0;

  int maxLen = 1;

  bool table[1000][1000] = {false};

  for (int i = 0; i < n; i++) {

    table[i][i] = true;   //前期的初始化

  }

  for (int i = 0; i < n-1; i++) {

    if (s[i] == s[i+1]) {

      table[i][i+1] = true; //前期的初始化

      longestBegin = i;

      maxLen = 2;

    }

  }

  for (int len = 3; len <= n; len++) {

    for (int i = 0; i < n-len+1; i++) {

      int j = i+len-1;

      if (s[i] == s[j] && table[i+1][j-1]) {

        table[i][j] = true;

        longestBegin = i;

        maxLen = len;

      }

    }

  }

  return s.substr(longestBegin, maxLen);

}

 

方法三 中心扩展法

    这个算法思想其实很简单啊,时间复杂度为O(N2),空间复杂度仅为O(1)。就是对给定的字符串S,分别以该字符串S中的每一个字符C为中心,向两边扩展,记录下以字符C为中心的回文子串的长度。但是有一点需要注意的是,回文的情况可能是 a b a,也可能是 a b b a。

string expandAroundCenter(string s, int c1, int c2) {

  int l = c1, r = c2;

  int n = s.length();

  while (>= 0 && r <= n-1 && s[l] == s[r]) {

    l--;

    r++;

  }

  return s.substr(l+1, r-l-1);

}

string longestPalindromeSimple(string s) {

  int n = s.length();

  if (== 0) return "";

  string longest = s.substr(0, 1);  // a single char itself is a palindrome

  for (int i = 0; i < n-1; i++) {

    string p1 = expandAroundCenter(s, i, i);

    if (p1.length() > longest.length())

      longest = p1;

    string p2 = expandAroundCenter(s, i, i+1);

    if (p2.length() > longest.length())

      longest = p2;

  }

  return longest;

}

 

  方法四 传说中的Manacher算法。时间复杂度O(N)

    这个算法有一个很巧妙的地方,它把奇数的回文串和偶数的回文串统一起来考虑了。这一点一直是在做回文串问题中时比较烦的地方。这个算法还有一个很好的地方就是充分利用了字符匹配的特殊性,避免了大量不必要的重复匹配。

    算法大致过程是这样。先在每两个相邻字符中间插入一个分隔符,当然这个分隔符要在原串中没有出现过。一般可以用‘#’分隔。这样就非常巧妙的将奇数长度回文串与偶数长度回文串统一起来考虑了(见下面的一个例子,回文串长度全为奇数了),然后用一个辅助数组P记录以每个字符为中心的最长回文串的信息。P[id]记录的是以字符str[id]为中心的最长回文串,当以str[id]为第一个字符,这个最长回文串向右延伸了P[id]个字符。
    原串:    w aa bwsw f d
    新串:           #  w  # a # a #  b  # w # s # w #  f  # d #
辅助数组P: 1  2  1 2 3 2 1  2  1  2 1 4 1 2 1  2 1 2 1

这里有一个很好的性质,P[id]-1就是该回文子串在原串中的长度(包括‘#’)。如果这里不是特别清楚,可以自己拿出纸来画一画,自己体会体会。当然这里可能每个人写法不尽相同,不过我想大致思路应该是一样的吧。

现在的关键问题就在于怎么在O(n)时间复杂度内求出P数组了。只要把这个P数组求出来,最长回文子串就可以直接扫一遍得出来了。

    那么怎么计算P[i]呢?该算法增加两个辅助变量(其实一个就够了,两个更清晰)id和mx,其中id表示最大回文子串中心的位置,mx则为id+P[id],也就是最大回文子串的边界。

    然后可以得到一个非常神奇的结论,这个算法的关键点就在这里了:如果mx > i,那么

P[i] >= MIN(P[2 * id - i], mx - i)。就是这个串卡了我非常久。实际上如果把它写得复杂一点,理解起来会简单很多:

//j = 2 * id - i,也就是说 是 关于 id 的对称点。
if (mx - i > P[j]) 
    P[i] = P[j];
else /* P[j] >= mx - i */
    P[i] = mx - i; // P[i] >= mx - i,取最小值,之后再匹配更新。


当 mx - i > P[j] 的时候,以S[j]为中心的回文子串包含在以S[id]为中心的回文子串中,由于 i 和 j 对称,以S[i]为中心的回文子串必然包含在以S[id]为中心的回文子串中,所以必有 P[i] = P[j]。

当 P[j] > mx - i 的时候,以S[j]为中心的回文子串不完全包含于以S[id]为中心的回文子串中,但是基于对称性可知,也就是说以S[i]为中心的回文子串,其向右至少会扩张到mx的位置,也就是说 P[i] >= mx - i。至于mx之后的部分是否对称,就只能老老实实去匹配了。

由于这个算法是线性从前往后扫的。那么当我们准备求P[i]的时候,i以前的P[j]我们是已经得到了的。我们用mx记在i之前的回文串中,延伸至最右端的位置。同时用id这个变量记下取得这个最优mx时的id值。(注:为了防止字符比较的时候越界,我在这个加了‘#’的字符串之前还加了另一个特殊字符‘$’,故我的新串下标是从1开始的)

#include<vector>
#include<iostream>
using namespace std;

const int N=300010;
int n, p[N];
char s[N], str[N];

#define _min(x, y) ((x)<(y)?(x):(y))

void kp()
{
    int i;
    int mx = 0;
    int id;
    for(i=n; str[i]!=0; i++)
        str[i] = 0; //没有这一句有问题。。就过不了ural1297,比如数据:ababa aba
    for(i=1; i<n; i++)
    {
        if( mx > i )
            p[i] = _min( p[2*id-i], p[id]+id-);
        else
            p[i] = 1;
        for(; str[i+p[i]] == str[i-p[i]]; p[i]++)
            ;
        if( p[i] + i > mx )
        {
            mx = p[i] + i;
            id = i;
        }
    }
}

void init()
{
 int i, j, k;
 str[0] = '$';
 str[1] = '#';
 for(i=0; i<n; i++)
 {
  str[i*2+2] = s[i];
  str[i*2+3] = '#';
 }
 n = n*2+2;
 s[n] = 0;
}

int main()
{
 int i, ans;
 while(scanf("%s", s)!=EOF)
 {
  n = strlen(s);
  init();
  kp();
  ans = 0;
  for(i=0; i<n; i++)
   if(p[i]>ans)
    ans = p[i];
  printf("%d\n", ans-1);
 }
 return 0;
}

 

if( mx > i)

p[i]=MIN( p[2*id-i], mx-i);

 

就是当前面比较的最远长度mx>i的时候,P[i]有一个最小值。这个算法的核心思想就在这里,为什么P数组满足这样一个性质呢?

(下面的部分为图片形式)

LEETCODE上也有这个题的详细说明,不过是英文版本的。

http://www.leetcode.com/2011/11/longest-palindromic-substring-part-ii.html

 

 

和如下文章:

http://blog.csdn.net/alongela/article/details/8208303

 

回文串:如果某个字符串从左往右看与从右往左看是一样的,则称该字符串为回文串。

最长回文子串:求解一个字符串,它是某个给定字符串的子串,并且是回文串。

本题要求对于给定的字符串,求出它的最长回文子串的长度。可以采用Manacher算法在O(n)时间内求解。

[cpp] view plaincopy

1. #include <iostream>  

2. #include <cstdio>  

3. #include <cstring>  

4. #include <algorithm>  

5. using namespace std;  

6.   

7. const int N = 1000005;  

8. char str[N];    //原字符串  

9. char s[N << 1];       //用'#'作为间隔填充之后的字符串  

10. int p[N << 1];        //p[i]表示以第i个字符为中心的回文子串的长度加1  

11. int len;  

12. int ans;    //最长回文子串的长度  

13.   

14. void Manacher()  

15. {  

16.     int i;  

17.     int mx; //mx表示当前回文子串扩展的最右端  

18.     int id; //id表示mx是由哪个回文子串扩展出来的  

19.     len = (strlen(str) + 1) << 1;  

20.     for (i = 0; i < len; ++i)    //构造填充字符串  

21.     {  

22.         s[i] = '#';  

23.         p[i] = 0;  

24.     }  

25.     for (i = 0; str[i] != 0; ++i) s[(i + 1) << 1] = str[i];  

26.     s[(i + 1) << 1] = 0;  

27.     mx = 0;  

28.     ans = 0;  

29.     for (i = 1; s[i] != 0; ++i) //求解p数组  

30.     {  

31.         if (mx > i) p[i] = min(mx - i, p[(id << 1) - i]);  

32.         else p[i] = 1;  

33.         while (s[i - p[i]] == s[i + p[i]]) ++p[i];  

34.         if (i + p[i] > mx)  

35.         {  

36.             mx = i + p[i];  

37.             id = i;  

38.         }  

39.         if (p[i] - 1 > ans) ans = p[i] - 1;  //更新最长回文子串的长度  

40.     }  

41. }  

42.   

43. int main()  

44. {  

45.     int Case = 0;  

46.     while (scanf("%s", str) != EOF)  

47.     {  

48.         if (strcmp(str, "END") == 0) break;  

49.         Manacher();  

50.         printf("Case %d: %d\n", ++Case, ans);  

51.     }  

52.     return 0;  

53. }  

 

本题也可以使用后缀数组求解,但是使用后缀数组大概需要用10s左右的时间,而使用Manacher算法需要的时间不到300ms

 

你可能感兴趣的:(华三面试题一)