经典算法面试与解答(二)
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;
}