第一题: 1 的数目
给定一个十进制正整数N, 写下从1开始,到N的所有整数,然后数一下其中出现所有“1”的个数,即求f(N)。
例如:
N=2, 写下1,2,。 出现1个1;
N=13, 我们写下:1,2,3,4,5,6,7,8,9,10,11,12,13,出现1个的个数是6.
求解函数f(N),即返回1 到N之间出现的 1 的个数,如f(13)=6.
ULONGLONG COuntInAteger(ULONGLONG n) //求解一个数(如11)中1的个数
{
ULONGLONG iNum = 0;
while(n!=0)
{
iNum=iNum+(n%10==1)?1:0;
n=n/10;
}
return iNum;
}
ULONGLONG f(ULONGULONG n) //用for循环逐个求解
{
ULONGLONG iCount=0;
for(ULONGLONG i=1;i <= n; i++)
{
iCount=iCount+COuntInAteger(i);
}
return iCount;
}
第二题: 求二进制数中1 的个数
对于一个字节(8bit)的无符号整型变量,求其二进制表示中“1”的个数,要求算法的执行效率尽可能高。
int Count(BYTE v)
{
int num=0;
while(v)
{
if(v % 2 ==1)
{
num++;
}
v=v/2;
}
return num;
}
int Count(BYTE v)
{
int num=0;
while(v)
{
v=v&(v-1);
num++;
}
return num;
}
第三题: 最大公约数问题
写一个程序,求两个正整数的最大公约数。如果两个正整数都很大,有什么简单的方法吗?
求最大公约数是一个很基本的问题。早在公元前300年左右,欧几里得就在他的著作《几何原本》中给出了高效的解法——辗转相除法。辗转相除法使用到的原理很聪明也很简单,假设用f(x, y)表示x,y的最大公约数,取k= x/y,b = x%y,则x = ky + b,如果一个数能够同时整除x和y,则必能同时整除b和y;而能够同时整除b和y的数也必能同时整除x和y,即x和y的公约数与b和y的公约数是相同的,其最大公约数也是相同的,则有f(x, y)=f(y, y % x)(y > 0),如此便可把原问题转化为求两个更小数的最大公约数,直到其中一个数为0,剩下的另外一个数就是两者最大的公约数。辗转相除法更详细的证明可以在很多的初等数论相关书籍中找到,或者读者也可以试着证明一下。
示例如下:
f(42, 30)=f(30, 12)= f(12, 6)=f(6, 0)= 6
【解法一】
最简单的实现,就是直接用代码来实现辗转相除法。从上面的描述中,我们知道,利用递归就能够很轻松地把这个问题完成。
具体代码如下:
int gcd(int x, int y)
{
return (!y) ? x : gcd( y, x%y ) ;
}
【解法二】
在解法一中,我们用到了取模运算。但对于大整数而言,取模运算(其中用到除法)是非常昂贵的开销,将成为整个算法的瓶颈。有没有办法能够不用取模运算呢?
采用类似前面辗转相除法的分析,如果一个数能够同时整除x和y,则必能同时整除x-y和y;而能够同时整x-y和y的数也必能同时整除x和y,即x和y的公约数与x-y和y的公约数是相同的,其最大公约数也是相同的,即f(x, y)= f(x-y, y),那么就可以不再需要进行大整数的取模运算,而转换成简单得多的大整数的减法。
在实际操作中,如果x<y,可以先交换(x, y)(因为(x, y)=(y, x)),从而避免求一个正数和一个负数的最大公约数情况的出现。一直迭代下去,直到其中一个数为0。
示例如下:
f(42, 30)=f(30, 12)=f(12, 18)= f(18, 12)= f(12, 6)= f(6, 6)= f(6, 0)= 6
解法二的具体代码如下:
代码清单2-15
BigInt gcd(BigInt x, BigInt y)
{
if(x < y)
return gcd(y, x);
if(y == 0)
return x;
else
return gcd(x - y, y);
}
第四题: 求数组的子数组之和的最大值
写一个程序,求两个正整数的最大公约数。如果两个正整数都很大,有什么简单的方法吗?
一个有N个整数元素的一维数组( A[0], A[1], … , A[n-2], A[n-1]),子数组之和的最大值是什么?(要求子数组的元素是连续的)
例子:有数组( -2, 5, 3, -6, 4, -8, 6),则其子数组之和的最大值为8,其对应的数组为(5,3)
解法一:采用直接法,记Sum[i…j],为数组A中从第i到第j之间所有数之和,算出所有Sum,取其最大,代码如下,时间复杂度O(N2):
int maxSum1(int *A, int n)
{
int max = -1;
int i, j, sum;
for(i = 0; i < n; i++)
{
sum = 0;
for(j = i; j < n; j++)
{
sum += A[j];
if(sum > max )
max = sum;
}
}
return max;
}
第五题 求二叉树中叶子节点的最大距离
struct Node
{
Node* pLeft; //左子树
Node* pRight; //右子树
int nMaxLeft; //左子树中的最长距离
int nMaxRight; //右子树中的最长距离
char chValue; //该节点的值
};
int nMaxLen = 0;
//寻找树中最长的两段距离
void findMaxLen(Node* pRoot)
{
//遍历到叶子节点,则返回
if (pRoot == NULL)
{
return;
}
//如果左子树为空,那么该节点的左边最长距离为0
if (pRoot->pLeft == NULL)
{
pRoot->nMaxLeft = 0;
}
//如果右子树为空,那么该节点的右边最长距离为0
if (pRoot->pRight == NULL)
{
pRoot->nMaxRight = 0;
}
//如果左子树不为空,递归寻找左子树最长距离
if (pRoot->pLeft != NULL)
{
findMaxLen(pRoot->pLeft);
}
//如果右子树不为空,递归寻找右子树最长距离
if (pRoot->pRight != NULL)
{
findMaxLen(pRoot->pRight);
}
//计算左子树最长节点距离
if (pRoot->pLeft != NULL)
{
int nTemp;
if (pRoot->pLeft->nMaxLeft > pRoot->pLeft->nMaxRight)
{
nTemp = pRoot->pLeft->nMaxLeft;
}
else
nTemp = pRoot->pLeft->nMaxRight;
pRoot->nMaxLeft = nTemp+1;
}
//计算右子树最长节点距离
if (pRoot->pRight != NULL)
{
int nTemp;
if (pRoot->pRight->nMaxLeft > pRoot->pRight->nMaxRight)
{
nTemp = pRoot->pRight->nMaxLeft;
}
else
nTemp = pRoot->pRight->nMaxRight;
pRoot->nMaxRight = nTemp+1;
}
//更新最长距离
if (pRoot->nMaxLeft+pRoot->nMaxRight > nMaxLen)
{
nMaxLen = pRoot->nMaxLeft+pRoot->nMaxRight;
}
}