经典算法面试与解答(二)

来源:互联网 发布:数据机房动环品牌 编辑:程序博客网 时间:2024/06/10 00:19

1、题目:输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数的非递归解法

例如输入12,从1到12这些整数中包含1 的数字有1,10,11和12,1一共出现了5次。

分析:

把该正整数表示为十进制的字符串:an-1an-2…a0,其中an-1为最高位,a0为最低位(个位),按最高位、最低位、中间位来分别分析其中1出现的次数:

  • 最高位(an-1)出现数字1的次数:

如果an-1 == 1,则为1bn-2…b0,且 0 <= 整数(bn-2…b0) <= 整数(an-2…a0),累计 (an-2…a0)+1个;

否则,an-1 > 1,则为1xn-2…x0,其中(xn-2…x0)为任意(n-1)位整数,累计10n-1个;

  • 最低位(a0)出现数字1的次数:

如果a0 == 0,则为bn-1…b11,且 0 <= 整数(bn-1…b1) < 整数(an-1…a1),累计 (an-1…a1)个;

否则a0 >= 1,则为bn-1…b11,且 0 <= 整数(bn-2…b0) <= 整数(an-1…a1),累计 (an-1…a1)+1个;

  • 中间位(ai):ai出现数字1的次数,0 < i < (n-1):

如果ai == 0,则为bn-1…bi+11xi-1…x0,且 0 <= 整数(bn-1…bi+1) < 整数(an-1…ai+1),(xi-1…x0)为任意i位数,累计 (an-1…ai+1)*10i个;

如果ai == 1,则除了ai == 0的情形外,还有一种情形:an-1…ai+11ci-1…c0,且0 <= (ci-1…c0) <= ai-1…a0,有(ai-1…a0)+1个,累计:(an-1…ai+1)*10i+(ai-1…a0)+1,即(an-1…ai+1ai-1…a0)+1个;

否则,ai > 1,则为bn-1…bi+11xi-1…x0,且 0 <= 整数(bn-1…bi+1) <= 整数(an-1…ai+1),(xi-1…x0)为任意i位数,累计 ((an-1…ai+1)+1)*10i个;

 

2、实现一个挺高级的字符匹配算法:
例如:“123” 这3个字符,要匹配到:
“1   3  2”     “3(随便一系列的字符)2(随便一系列的字符)1  ”(123的顺序可以打乱)。
其实就是说给一串很长字符串,要求找到符合要求的字符串,例如目的串:123
1******3***2 ,12*****3这些都要找出来
采用Hash法,
哈希表是一种比较复杂的数据结构。由于比较复杂,STL中没有实现哈希表,因此需要我们自己实现一个。但由于本题的特殊性,我们只需要一个非常简单的哈希表就能满足要求。由于字符(char)是一个长度为8的数据类型,因此总共有可能256 种可能。于是我们创建一个长度为256的数组,每个字母根据其ASCII码值作为数组的下标对应数组的对应项,而数组中存储的0、1对应每个字符是否出现。这样我们就创建了一个大小为256,以字符ASCII码为键值的哈希表。(并不仅限于英文字符,所以这里要考虑256种可能)。

       知道了这点,我们可以构建一个数组来统计模式串中某个字符是否出现,然后在对目标串进行扫描,看看对应的所有位上是否出现,从而判断是否匹配。分析一下复杂度,大概是O(m+n)。

 

//强大的和谐系统
int is_contain(char *src, char *des)
{
 //创建一个哈希表,并初始化
 const int tableSize = 256;
 int hashTable[tableSize];
 int len,i;
 for(i = 0; i < tableSize; i++)
  hashTable[i] = 0;
 len = strlen(src);
 for(i = 0; i < len; i++)
  hashTable[src[i]] = 1;

 len = strlen(des);
 for(i = 0; i < len; i++)
 {
  if(hashTable[des[i]] == 0)
   return 0;         //匹配失败
 }
 return 1;    //匹配成功
}

 

3、有两个序列a,b,大小都为n,序列元素的值任意整数,无序;
要求:通过交换a,b中的元素,使[序列a元素的和]与[序列b元素的和]之间的差最小。

求解思路:

    当前数组a和数组b的和之差为
    A = sum(a) - sum(b)

    a的第i个元素和b的第j个元素交换后,a和b的和之差为
    A' = sum(a) - a[i] + b[j] - (sum(b) - b[j] + a[i])
           = sum(a) - sum(b) - 2 (a[i] - b[j])
           = A - 2 (a[i] - b[j])
    设x = a[i] - b[j]

    |A| - |A'| = |A| - |A-2x|

   
    |A'|= |A-2x|

    假设A > 0,

    当x 在 (0,A)之间时,做这样的交换才能使得交换后的a和b的和之差变小,x越接近A/2效果越好,

    如果找不到在(0,A)之间的x,则当前的a和b就是答案。

    所以算法大概如下:

    在a和b中寻找使得x在(0,A)之间并且最接近A/2的i和j,交换相应的i和j元素,重新计算A后,重复前面的步骤直至找不到(0,A)之间的x为止。

 4、求一个矩阵中最大的二维矩阵(元素和最大).如:
1 2 0 3 4
2 3 4 5 1
1 1 5 3 0
中最大的是:
4 5
5 3
要求:(1)写出算法;(2)分析时间复杂度;(3)用C写出关键代码
分析:先求解一维数组最大和,再求解二维。

假设最大子矩阵的结果为从第r行到k行、从第i列到j列的子矩阵,
如下所示(ari表示a[r][i],假设数组下标从1开始):

  | a11 …… a1i ……a1j ……a1n |
  | a21 …… a2i ……a2j ……a2n |
  .....

  | ar1 …… ari ……arj ……arn |     第r行 . . .
  ..........                            | 
                                  V
  | ak1 …… aki ……akj ……akn |   第k行 . . .

  .....
  | an1 …… ani ……anj ……ann |

 那么我们将从第r行到第k行的每一行中相同列的加起来,可以得到一个一维数组如下:
 (ar1+……+ak1, ar2+……+ak2, ……,arn+……+akn)
 由此我们可以看出最后所求的就是此一维数组的最大子断和问题。

#include <iostream>
using namespace std;
struct Point
{
 short int x;
 short int y;
};

//求一维数组的最大和
int getMaxSumInArray(int data[], int nSize, int& start, int& end)
{
 int sum = data[0];
 int maxSum = data[0];
 for( int i=1; i<nSize; i++)
 {
  if(sum>0)
   sum+=data[i];
  else
  {
   sum = data[i];
   start = i;
   end = i;
  }
  if(sum>maxSum)
  {
   maxSum = sum;
   end = i;
  }
 }
 return maxSum;
}

//二维
void getMaxSumInMartrix(int data[100][100], int m, int n)
{
 int* rowArray = new int[n];
 int maxSum = data[0][0];
 int start = 0, end = 0;
 Point leftTop = {0,0},rightBottom = {0,0};
 // i代表从第几行开始加
 for(int i=0; i<m; i++)
 {
  //j代表加到第几行结束
  for(int j=i; j<m; j++)
  {
   memset(rowArray,0,sizeof(int)*n);
   //从第i行加到第j行
   for(int k=0; k<n; k++)
   {
    for( int t=i; t<=j; t++)
     rowArray[k]+=data[t][k];
   }
   int sum = getMaxSumInArray(rowArray,n,start,end);
   if(sum>maxSum)
   {
    maxSum = sum;
    leftTop.x = i;
    leftTop.y = start;
    rightBottom.x = j;
    rightBottom.y = end;
   }
  }
 }
 cout<<"最大和为"<<maxSum<<endl;
 cout<<"起始位置为("<<leftTop.x<<","<<leftTop.y<<")"<<endl;
 cout<<"结束位置为("<<rightBottom.x<<","<<rightBottom.y<<")"<<endl;
}
int main()
{
 int data[100][100];
 int m,n;
 cin>>m;
 cin>>n;
 for( int i=0; i<m; i++)
  for( int j=0; j<n; j++)
   cin>>data[i][j];
 getMaxSumInMartrix(data,m,n);
 return 0;
}

5、谷歌笔试:
n支队伍比赛,分别编号为0,1,2。。。。n-1,已知它们之间的实力对比关系,
存储在一个二维数组w[n][n]中,w[i][j] 的值代表编号为i,j的队伍中更强的一支。

所以w[i][j]=i 或者j,现在给出它们的出场顺序,并存储在数组order[n]中,
比如order[n] = {4,3,5,8,1......},那么第一轮比赛就是 4对3, 5对8。.......

胜者晋级,败者淘汰,同一轮淘汰的所有队伍排名不再细分,即可以随便排,
下一轮由上一轮的胜者按照顺序,再依次两两比,比如可能是4对5,直至出现第一名

编程实现,给出二维数组w,一维数组order 和 用于输出比赛名次的数组result[n],求出result。

分析:采用队列存储比赛的队伍,代码如下:

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

void raceResult(int w[5][5], int* order, int* result, int n)
{
 int count = n;
 queue<int> raceQueue;
 for( int i=0; i<n; i++)
  raceQueue.push(order[i]);
 while (raceQueue.size()>0)
 {
  if(raceQueue.size()>1)
  {
   int first = raceQueue.front();
   raceQueue.pop();
   int second = raceQueue.front();
   raceQueue.pop();
   if( w[first][second] == first)
   {
    raceQueue.push(first);
    result[--count] = second;
    
   }
   else
   {
    raceQueue.push(second);
    result[--count] = first;
   }

  }
  else
  {
   result[0] = raceQueue.front();
   raceQueue.pop();
  }
 }

}
int _tmain(int argc, _TCHAR* argv[])
{
 const int n=5;
 int w[n][n] = {0,1,2,3,4,
     1,1,1,1,1,
     2,1,2,2,4,
     3,1,2,3,4,
     4,1,4,4,4
 };
 int order[n] = {3,2,1,0,4};
 int result[n];
 raceResult(w,order,result,n);
 for( int i=0; i<n; i++)
  cout<<result[i]<<" ";
 cout<<endl;
 return 0;
}