读书人

数据结构算法笔考试题

发布时间: 2013-09-27 14:23:42 作者: rapoo

数据结构算法笔试题

1金币概率问题(威盛笔试题)

题目:个房间里放着随机数量的金币。每个房间只能进入一次,并只能在一个房间中拿金币。一个人采取如下策略:前四个房间只看不拿。随后的房间只要看到比前四个房间都多的金币数,就拿。否则就拿最后一个房间的金币。编程计算这种策略拿到最多金币的概率。

这题真要用数学的方法计算,估计还真不好算。还好,题目要求用编程实现。这样它就成了一个模拟题,即用程序来模拟整个取金币的过程。

我们可以进行很多次实验(如10000次)。每次实验,对每个房间产生随机数量的金币数,然后按照题目中的策略拿金币。如果拿到的金币数恰好是最多的则成功。最后统计很多次实验中成功的次数,并计算概率。

[cpp] view plaincopy
  1. #include <iostream>
  2. #include <ctime>
  3. using namespace std;
  4. const int MAX_COIN = 100;
  5. const int MIN_COIN = 1;
  6. //初始化随机数种子
  7. void InitRandom()
  8. {
  9. srand( time( NULL ) );
  10. }
  11. //为每个房间产生随机数量的金币
  12. int GegenrateGoldCoin( int *goldCoin, int size )
  13. {
  14. int max = 0;
  15. for( int i=0; i<size; i++ )
  16. {
  17. goldCoin[i] = ( rand()%( MAX_COIN - MIN_COIN + 1) ) + MIN_COIN;
  18. if( goldCoin[i] > max ) max = goldCoin[i];
  19. }
  20. //范围最多的金币数
  21. return max;
  22. }
  23. //按照给定的策略从房间中拿金币
  24. int TakeCoin( int *goldCoin, int size )
  25. {
  26. int firstFour[4];
  27. int maxInFirstFour = 0;
  28. for( int i=0; i<4; i++ )
  29. {
  30. firstFour[i] = goldCoin[i];
  31. if( goldCoin[i] > maxInFirstFour ) maxInFirstFour = goldCoin[i];
  32. }
  33. for( int i=4; i<size; i++ )
  34. {
  35. //如果比前四个房间的金币都多,则拿
  36. if( goldCoin[i] > maxInFirstFour ) return goldCoin[i];
  37. }
  38. //拿最后一个房间的金币
  39. return goldCoin[size-1];
  40. }
  41. int main()
  42. {
  43. int goldCoin[10];
  44. int tryCnt = 10000;
  45. int successCnt = 0;
  46. InitRandom();
  47. //总共进行tryCnt次实验
  48. for( int i=0; i<tryCnt; i++ )
  49. {
  50. int max = GegenrateGoldCoin( goldCoin, 10 );
  51. int choose = TakeCoin( goldCoin, 10 );
  52. if( max == choose ) successCnt++;
  53. }
  54. cout << successCnt * 1.0 / tryCnt << endl;
  55. return 0;
  56. }

2.找出数组中唯一的重复元素

1-1000放在含有个元素的数组中,只有唯一的一个元素值重复,其它均只出现一次.每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空间,能否设计一个算法实现?

设数组为A[1001] = { a1, a2, …, a1001 },重复的元素为x, 且 1 <= x <=1000。

SumA = 1+…+1000

SumB = a1 + … + a1001

所以,唯一重复的元素为:x = SumB SumA

要注意的问题:

1. 唯一重复的元素。这点很重要,如果有不止一个重复的元素,要找出其中任意一个,就不会这么简单了。

2. 注意溢出的情况。和的范围:(1+1000)*1000/2 ≈ 1000^2 ≈ 2^20。具体编程实现的时候,使用4字节的int完全可以搞定。如果数据范围很大,比如数组中存放的元素[1, 2^40],此时和的范围(1+2^40)*2^40/2 ≈ 2^80,远远超过了8字节的long long的表示范围,求和时显然会溢出。

3.百度校园招聘的一道笔试题

题目大意如下:

一排N个正整数,其中最大值1M,且+1递增,乱序排列。第一个不是最小的,把它换成-1,最小数为a且未知,求第一个被-1替换掉的数原来的值,并分析算法复杂度。

同上一题基本相同。

设这一排数是A1、A2、A3、…、AN,这N个数分别是: a, a+1, a+2, …, a+n

被替换掉的数为X。

SumA = A1+A2+A3+…+AN

SumB =a+(a+1)+…+(a+n)

则 X + 1 = SumB SumA

处理溢出情况:

和的最大范围a + … + 2^20 ≈ 1+…+ 2^20 ≈ (1+2^20)* 2^20/2 =2^40。使用4字节的int会溢出。

下面有种方法,可以进行一个简单的处理,但处理能力有限。

使用辅助数组data,数组的元素是Ai-(a+i-1)。则data的所有元素之和恰好是SumB SumA。现在要说明的是:对data的所有元素求和不会溢出。

最好情况下,这一排数{A1、A2、A3、…、AN}的顺序基本和{ a, a+1, a+2, …, a+n }相同,这样除了第一个元素,其余元素对应相减都为0,因此不会溢出。

最坏情况下,{A1、A2、A3、…、AN}递减排列,{ a, a+1, a+2, …, a+n }递增排列。此时,data的前N/2个元素为正,后N/2个元素为负。相加求和时,只要前N/2个元素的和不溢出,则结果不溢出。这时,前N/2个元素分别为:

(a+n)-(a), (a+n-1)-(a+1), (a+n-2)-(a+2),…2, 0

则,前N/2个元素的和:(((a+n)-(a))*n/2)/2 = n^2/4≈(2^20)^2/4≈ 2^40

3.一道SPSS笔试题求解

题目:输入四个点的坐标,求证四个点是不是一个矩形

关键点:

1.相邻两边斜率之积等于-1,

2.矩形边与坐标系平行的情况下,斜率无穷大不能用积判断。

3.输入四点可能不按顺序,需要对四点排序。

算法步骤:

1.首先,对这四个点按照x坐标从小到大排序,设这四个点分别为A、B、C、D。

2. 如果A.x == B.x,即如果是矩形,则与坐标轴平行。

即要求C.x == D.x&&( ( A.y == C.y && B.y == D.y ) || ( A.y == D.y && B.y== C.y ) )

3. 如果A.x != B.x,则计算四条边的斜率Kab、Kac、Kdb、Kdc。如果是矩形,则有三个内角都为90度。

即要求 Kab*Kac== -1 && Kdb*Kdc == -1 && Kac*Kdc == -1.

数据结构算法笔考试题

4.求两个或N个数的最大公约数和最小公倍数。

求两个数的最大公约数,即gcd( a, b ) = ?。先不管最大公约数怎么求,一旦已知最大公约数,就可以很容易得到最小公倍数。两个数的最小公倍数 = a * b / gcd( a, b)

最大公约数可以采用经典的辗转相差法。设这两个数分别是a和b, 且a > b.要证明辗转相差法,即要证明 gcd( a, b ) = gcd( b, r ),其中r = a mod b

设 c = gcd( a, b ),即 a = mc, b = nc.

且r = a tb = mc tnc = ( m tn ) c

因此,gcd( b, r ) = gcd( nc, ( m tn ) c ) = gcd( n, ( m tn ) ) * c

即,现在要证明gcd( n, ( m tn ) ) * c = c

即,要证明n, ( m tn )互为质数。

再用反证法。即n, ( m tn )存在公约数d,且d != 1

设n = xd,m tn =yd,则m = yd + tn = yd + txd = (y+tx)d

即n = xd,m = (y+tx)d, 故gcd( a, b ) = gcd( mc,nc ) = cd != c,故矛盾

所以n, ( m tn )互为质数

即gcd( a, b ) = gcd( b, r )

[cpp] view plaincopy
  1. //求a、b的最大公约数
  2. int GetGCD( int a, int b )
  3. {
  4. if( a < b )
  5. {
  6. //交换a、b值
  7. a = a + b;
  8. b = a - b;
  9. a = a - b;
  10. }
  11. //辗转相除
  12. while( b > 0 )
  13. {
  14. int r = a % b;
  15. a = b;
  16. b = r;
  17. }
  18. return a;
  19. }

还有一个问题:如何求3个数的最大公约数、最小公倍数?

5.字符串原地压缩

题目描述:“eeeeeaaaff" 压缩为 "e5a3f2",请编程实现。

多媒体压缩里的行程编码。当大量字符连续重复出现时,压缩效果惊人。编程实现比较简单,统计重复的字符个数,然后把个数转化为字符串接在原字符之后。具体编程,见代码:用两个计数指针i, j扫描字符串。i始终指向字符的第一次出现,j指向字符的最后一次出现+1。至于int转string,这里使用stringstream
[cpp] view plaincopy
  1. //字符串的原地压缩,即行程编码、游程编码
  2. void StrCompress( char *original, char *cmpr )
  3. {
  4. if( original == NULL )
  5. {
  6. cmpr = NULL;
  7. return;
  8. }
  9. int cnt = 0;
  10. int i,j;
  11. for( i=0, j=0; *(original+j) != '\0'; )
  12. {
  13. //统计相同字符的个数
  14. while( *( original + i ) == *( original + j ) )
  15. {
  16. cnt++;
  17. j++;
  18. }
  19. //复制字符
  20. *cmpr++ = *( original + i );
  21. //复制字符个数
  22. stringstream ss;
  23. ss << cnt;
  24. string strCnt;
  25. ss >> strCnt;
  26. const char *pcstr = strCnt.c_str();
  27. while( *pcstr != '\0' ) *cmpr++ = *pcstr++;
  28. cnt = 0;
  29. i = j;
  30. }
  31. *cmpr++ = '\0';
  32. }

6.字符串匹配实现

请以两种方法,回溯与不回溯算法实现。

回溯法,即最基本的方法。算法复杂度O( m * n )

设主串mainStr = { S0, S1, S2, …, Sm },

模式串matchStr = { T0, T1, T2, …, Tn };

当T[0]…T[j-1] == S[i-j]…S[i-1],即模式串的前j个字符已经和主串匹配,当前要比较T[j]和S[i]是否相等?

如果T[j] == S[i], 则i++, j++,继续比较下一个

如果T[j] != S[i], 则i要回溯,也就是i要退回到与j开始匹配时的下一个位置。同时j=0, 表示模式串从头开始,重新匹配。

不回溯:即用KMP算法。算法复杂度O( m + n )。

在KMP中,如果T[j] != S[i],则i保持不动(即,不回溯)。同时,j不用清零,而是向右滑动模式串,用T[k]和S[i]继续匹配。

算法的关键在于:模式串向右滑动多少?即K=?显然,k的值应该尽可能的大,即尽可能的向右滑动。

数据结构算法笔考试题

如图,如果模式串T[0]...T[j-1]前后两部分对称,也就是T[0]…T[k-1] == T[j-k]…T[j-1],则模式串可以向右滑动k个距离,即用T[k]和S[i]继续匹配。

因此 K = Max{ x | 0<=x<=j, 且T[0]…T[x-1] == T[j-x]…T[j-1]}

由上面的分析可以对于任意的j,都对应一个k,于是我们把所有的K放到一个next数组中。数组元素next[j]=k,表示当T[j]匹配失败时,下一次应该用T[k]继续匹配。现在要解决的问题就是:如何求next数组的值?当然,通过上面的理解,可以直接写出简单的字符串的next,这里我们的目标是给出一个求next的通用的方法。

求next可以用一个递归的过程。已知next[j] = k, 求next[j+1] = ?

如果T[j] == T[k],则next[j+1] = k+1

如果T[j] != T[k],则next[j+1] = ?。

这时就相当于用T[k]去匹配T[j],且匹配失败。那么,我们就应该在T[0]…T[k-1]中找到一个合适的位置x,使得T[0]…T[x-1] == T[k-x]…T[k-1]。也就是说,当用T[k]去匹配T[j]失败时,我们应该用T[x]去匹配T[j]。因此x = next[k]。整个过程相当于用模式串去匹配自身。

[cpp] view plaincopy
  1. #include <iostream>
  2. #include <cassert>
  3. using namespace std;
  4. //求next数组
  5. //next[j] = k:表示当matchStr[j]失配时,下一次应该用matchStr[k-1]来匹配
  6. void GetNext( char *str, int *next )
  7. {
  8. if( str == NULL ) return;
  9. for( int i=0; *(str+i) != '\0'; i++ )
  10. {
  11. if( i == 0 ) next[i] = 0;
  12. else if( i == 1 ) next[i] = 1;
  13. else
  14. {
  15. int tmp = next[i-1];
  16. if( str[i-1] == str[tmp-1] ) next[i] = tmp+1;
  17. else
  18. {
  19. //如果str[0]...str[j]前后两端有对称,找出对称位置
  20. while( tmp > 1 )
  21. {
  22. if( str[i-1] != str[tmp-1] ) tmp = next[tmp];
  23. else next[i] = tmp+1;
  24. }
  25. //如果str[0]...str[j]前后两端无对称,则next置1
  26. if( tmp <= 1 ) next[i] = 1;
  27. }
  28. }
  29. }
  30. }
  31. //字符串匹配:KMP算法,即在mainStr中找到从beginPos开始的第一个匹配位置
  32. int Kmp( char *mainStr, char *matchStr, int beginPos, int *next )
  33. {
  34. assert( mainStr != NULL && matchStr != NULL && beginPos >= 0 );
  35. int i, j;
  36. for( i=beginPos, j=0; *(mainStr+i) != '\0' && *(matchStr+j) != '\0'; )
  37. {
  38. //如果mainStr[i] == matchStr[j], 继续匹配下一个
  39. if( *(mainStr+i) == *(matchStr+j) )
  40. {
  41. i++; j++;
  42. }
  43. //如果mainStr[i] != matchStr[j],查询next数组,
  44. //用matchStr[next[j]-1]与mainStr[i]匹配
  45. else j = next[j]-1;
  46. }
  47. if( *(matchStr+j) == '\0' ) return i-j;
  48. else return -1;
  49. }
  50. //字符串匹配的一般算法,要回溯
  51. int StrMatch( char *mainStr, char *matchStr, int beginPos )
  52. {
  53. int i, j;
  54. for( i = beginPos; *(mainStr+i) != '\0'; i++ )
  55. {
  56. int tmp = i;
  57. for( j=0; *(matchStr+j) != '\0'; )
  58. {
  59. if( *(mainStr+tmp) == *(matchStr+j) )
  60. {
  61. tmp++; j++;
  62. }
  63. else break;
  64. }
  65. if( *(matchStr+j) == '\0' ) return tmp-j;
  66. }
  67. return -1;
  68. }
  69. int main()
  70. {
  71. int next[100];
  72. memset( next, 0, sizeof(next) );
  73. char *mainStr = "ababcabcacbab";
  74. char *matchStr = "abcac";
  75. GetNext( matchStr, next );
  76. cout << Kmp( mainStr, matchStr, 0, next ) << endl;
  77. cout << StrMatch( mainStr, matchStr, 0 ) << endl;
  78. return 0;
  79. }

7.取值为[1,n-1] 含n 个元素的整数数组至少存在一个重复数,O(n) 时间内找出其中任意一个重复数。

可以使用类似单链表求环的方法解决这个问题。把数组想想成一个链表,这里用数组元素的值作为下一个元素在数组中的索引。

设数组A共有n个元素,即A={ a0, a1, a2, …, an-1 }。

首先给出下标n-1,则第一个元素为A[n-1],然后用A[n-1]-1作为下标,可以到达元素A[A[n-1]-1],再以A[A[n-1]-1]为下标,可以得到元素A[A[A[n-1]-1]]…可以看到这里并没用直接用元素值作索引,而是用元素值减1,这样做是为了避免陷入死循环。

如果A[i]=A[j]=x,即x在数组中出现了两次。则A[i]--->A[x]--->…---> A[j]---> A[x],因此链表边形成了环。

一旦链表产生后,问题就简单多了。因为重复出现得到元素恰好是环的入口点。于是,问题就相当于单链表求环的入口点。用指针追过的办法,指针x每次步长为2,指针y每次步长为1。直到x、y相遇,然后重置x,使x重新开始。这次同步移动x、y,每次步长都为1,当x、y再次相遇时,恰好是环的入口点。

[cpp] view plaincopy
  1. //在O(n)的时间内,找出任意重复的一个数
  2. int FindRepeat( int *data, int size )
  3. {
  4. int x = size;
  5. int y = size;
  6. //找到相遇点
  7. do{
  8. x = data[data[x-1]-1];
  9. y = data[y-1];
  10. }while( x != y );
  11. //找到重复的元素
  12. x = size;
  13. do{
  14. x = data[x-1];
  15. y = data[y-1];
  16. }while( x != y );
  17. return x;
  18. }

读书人网 >移动开发

热点推荐