2.1 求二进制数中1的个数
解法一:暴力的解法
对N中二进制1的个数:N = b[0] +b[1]*2+b[2]*2^2+...b[n]*2^n
int count(BYTE v){
int num = 0 ;
while(v)
{
if(v%2==1){
num++;
}
v>>1;
}
return num;
}
解法二:位操作
如果该二进制最后位上为1,与0x01相与,结果为1;然后将二进制右移
int count(BYTE v)
{
int num = 0;
while(v)
{
num += v&0x01 ;
v>>1;
}
return num;
}
解法三:v &(v-1)
int count(BYTE v){
int num = 0;
while(v){
v = v&(v-1);
sum ++;
}
return sum;
}
解法四:使用分支 (利用空间换取时间)
利用switch case语句,将每个数字的1的个数设置好,直接返回。
实际执行效率可能会低于方法二和三。具体要看分支执行情况。
方法五:查表法
提前将每个数1的个数放到一个表中,直接读取这个数组。
2.2 求N!中末尾0的个数 和二进制表示中最低位1的位置。
解法1:最直接的解法是找出能是末尾为0的那些数,通过分析N!=(2^x)*(3^y)*(5^z).....
由于只有2*5=10,才能是末尾为0;由于N! 中2的个数远多于5,所以找0的个数实际上是找N!分解5的个数。
int ret = 0;
for(i=1;i<=N;i++)
{
j=i;
while(j%5==0){
ret++;
j/=5;
}
}
解法2:Z= [N/5]+[N/5^2]+[N/5^3]+... 总会存在k使得[N/5^k]=0;
[N/5]所有小于等于N的数中5的倍数贡献一个5
[N/5^2]所有不大于N的书中5^2的倍数贡献一个5
int ret=0;
while(N){
ret += N/5;
N/=5;
25 ...20.... 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
N/5: 1 1 1 1 1
N/5^2 1
25!中包含6个5因子。故有6个0;
求二进制中最低位1的位置,需要确定二进制末尾有多少个0;由于一个数*2,其二进制向左移移位,0的个数即N!中包含多少个2因子。
解法1:
result = [N/2]+[N/2^2]+[N/2^3]+...
int lowestOne(int N){
int ret = 0 ;
while(N){
N>>1;
ret += N;
}
return ret;
}
解法2:
N!中因子2的个数=N-N中二进制1的数目。
假设N=11011;
N!中2因子的个数count = [N/2]+[N/2^2]+[N/2^3]+...
= 1101 +110+11+1
= (1000+100+1)+(100+10)+(10+1)+1
= (1111)+(111)+1
= (10000-1)+(1000-1)+(10-1)+(1-1)
= 11011 - N中1的个数
给定数N判断是否为2的方幂 n>0&&n&(n-1)==0
2.3 寻找发帖王水
找出那个ID号出现次数超过总数一半的ID。
通常做法是:先将所有ID排序,然后遍历ID统计ID出现的次数,找超过一般次数的ID。
对于已经有序的ID,则不需要对ID进行遍历,取N/2处的ID即是所找的ID。
有没有一种不需要排序就能找出王水的ID的方法?
每次取两个不同的ID,那么剩下的ID中王水出现的次数仍超过一半。从而不断的重复这个过程,直到找到答案。
Type Find(Type *ID,int N)
{
Type candidate;
int nTimes,i;
for(i=0; i<N;i++){
if(nTimes==0){
candidate = ID[i];
nTimes =1;
}
esle{
if(candidate == ID[i]){
nTimes ++;
}else{
nTimes--;
}
}
}
return candidate;
}
2.4 1的个数 写下1到N所有数中1的个数
设N= 12013,计算百位上1的个数的数字有百位数字、百位以上数字和百位一下数字决定。
12013如果百位数字为0;则百位数字为1的情况有100-199、1100-1199、……、11100-11199一共有12*100=1200个;
12113如果百位数字为1:则百位数字为1的情况有100-199、1100-1199、……、11100-11199、12100-12113一共有12*100+13+1=1214;
12313如果百位数字为其他:则百位数字为1的情况有100-199、1100-1199、……、11100-11199、12100-12199一共有(12+1)*100=1300;
所以需要计算当前位的值,低位的值,高位的值,
LONGLONG
Sum1s
(
ULONGLONG
n
)
{
ULONGLONG
iCount
=0;
ULONGLONG
iFactor
=1;
ULONGLONG
iLowerNum
=0;
ULONGLONG
iCurrentNum
=0;
ULONGLONG
iHighNum
=0;
while
(
n
/
iFactor
!=0){
iLowerNum
=
n
-
n
/
iFactor
*
iFactor
;
iCurrentNum
=
n
/
iFactor
%10;
iHighNum
=
n
/(
iFactor
*10);
switch
(
iCurrentNum
)
{
case
0:
iCount
+=
iHighNum
*
iFactor
;
break
;
case
1:
iCount
+=
iHighNum
*
iFactor
+
iLowerNum
+1;
break
;
case
2:
iCount
+= (
iHighNum
+1)*
iFactor
;
break
;
}
iFactor
*= 10;
}
return
iCount
;
}
2.5 寻找最大k个数
解法1: 排序 取前k个数 复杂度O(N*logN)+o(k)
解法2: 快排中的第一步,将待排数分成两组,其中一组的任意一个数比另外一组任意数要大,在对两个分组做类似的操作。。。
假设有数组S,从S中中随机找到一个元素x,把数组分成两部分Sa和Sb。Sa中的元素大于X,Sb中的元素小于X。这时:
1. Sa中的元素个数小于K,Sa中所有元素和Sb中最大的K-|Sa|个元素
2. Sa中的元素个数大于等于K,则需要直接返回Sa中最大的K个数。
方法4:利用最大堆最小堆。湘江K个元素放在最小堆中,每次新加入一个元素X,如果X比堆顶元素小,则不考虑,反之,将该元素替换堆顶元素,更新最小堆。时间复杂度为O(nlogK)
方法5: 利用桶排序,对于N个数取值范围有限,统计每个数出现的次数用数组count(MaxN)记录,从最大的数的个数取出大于K个数为止。
2.6 精确的表示浮点数
对于有限小数或者无限循环小数都可以转化为分数来存储。
例如:0.9= 9/10
1.333(3) = 1/3
对于有限小数X= 0.A1A2A3...An=A1A2...An/10^n
对于无限循环小数 X=0.A1A2A3...An(B1B2B3..Bm)
10^n*X=A1A2...An.(B1B2...Bm)
=A1A2..An+0.B1B2...Bm(B1B2...Bm) = A1A2...An +Y
Y= 0.B1B2...Bm(B1B2...Bm)
10^m*Y = B1B2...Bm+0.B1B2...Bm(B1B2...Bm)=B1B2...Bm+Y
Y=B1B2...Bm/(10^m-1)
X = (A1A2...An+B1B2..Bm/(10^m-1))/10^n
剩下的问题是找出分子和分母的最大公约数进行约分,求出最简分数。求最大公约数参考下一节。
2.7 最大公约数问题
f(x,y)=x,y的最大公约数,取k=x/y;b=x%y;则x=ky+b;(x>=Y)
如果一个数可以被x,y同时整除,则这个数必将可以被y,b整除,
f(x,y)=f(y,b)=f(y,x%y);
对于大整数来说,模运算带来的开销比较大,由于x,y的约数一定是x-y,y的约数,
f(x,y) = f(x-y,y)
BigInt gcd(Bigint X,BigInt Y){
if(x<y)
return gcd(y,x);
if(y==0)
return x;
else
return gcd (x-y,y)
}
但对于大整数来说,减法带来运算次数的开销比较大,尤其是gcd(10000000,1)这种情况。
对于x,y来说,如果x= kx1,y=ky1;则f(x,y)=k*f(x1,y1);
如果x=px1,p是素数,y%p!=0;则f(x,y)= f(x1,y);
对算法的改进:
取p=2;
当x,y都是偶数,f(x,y)=2*f(x>>1,y>>1)
当x为偶数,y为奇数,f(x,y)=f(x>>1,y)
当x为奇数,y为偶数,f(x,y)=f(x,y>>1);
当x,y都是奇数,f(x,y)=f(x-y,y)
BigInt gcd(BigInt x,BigInt y){
if(x<y)
return gcd(y,x);
if(y==0)
return x;
else
{
if(IsEven(x)){
if(IsEven(y)){
return gcd(x,y-x);
}else{
return gcd(x,y>>1);
}
}else{
if(IsEven(y)){
return gcd(x>>1,y);
}
else{
return 2*gcd(x>>1,y>>1);
}
}
}
}
2.9 Fibonacci数列
F(n) = 0 n=0
= 1 n=1
= F(n-1) + F(n-1) n>1
一般解法:
int Fibonacci(int n){
if(n==0)
return 0;
else if(n==1){
return 1;
}else
return Fibonacci(n-1) + Fibonacci(n-2);
}
在递归调用时通过数组记录已经计算过的值,这样以空间换取时间,时间复杂度为O(n)。
解法2: 求解通项公式
F(n)=F(n-1)+F(n-2)
特征方程为X^2=X +1
有根:X = (1+5^0.5)/2 X=(1-5^0.5)/2
F(n)=A*X1^n+B*X2^n
由F(0) = 0,F(1)=1;==>A=5^0.5/5 B=-A
由于引入了无理数,不能保证精度。
解法三:分治的方法
(F(n) F(n-1)) = (F(n-1) F(n-2))*A
==> A=1 1
1 0
(Fn Fn-1) = (Fn-1 Fn-2)A =... = (F1 F0)A^(n-1)
A^(x*2) = (A^x)^2
用二进制表示n:
n=b0 +b1*2 +b2*2*2 +...+bk*2^k
A^n = A^(b0 +b1*2 +b2*2*2 +...+bk*2^k)
= A^b0*(A^2)b1*(A^(2^2))^b2*...*(A^(2^k))^bk
所以求 A^(2^k) = (A^(2^(k-1)))^2
class Matrix;
Matrix MatrixPow(Const Matrix &m,int n)
{
Matirx result = Matrix::Identity;//单位阵
Matrix tmp= m;
for(;n;n>>1){
if(n&1){
result *= tmp;
}
tmp *=tmp;
}
}
int Fabonacci(int n){
Matrix an = MatrixPow(A,n-1);
return F0*an(0,0) +F1*an(1,0);
}
2.10 寻找数组中的最大值和最小值
解法1:穷举,找出数组中最大值和最小值,时间复杂度为2*N次,O(N);
解法2:将数组中的数按相邻两个放在一组,比较每组中较大的放在偶数位,较小的放在奇数位,需要N/2次比较
从偶数中比较选出最大的那个即为最大值,需要N/2次比较;
从奇数中比较选出较小的那个即为最小值,需要N/2次比较;总共需要1.5N次比较。
解法三:解法二破坏了原来数组的顺序,解法三是不破坏数组的顺序的。
利用两个变量max和min分别记录最大值和最小值,通过相邻的两个比较,去除较大的放在max中,取出较小的放在min中,然后再比较下两个数,将较小的与min比较,将较大的与max比较。
解法四:利用分治的思想,在前N/2和后N/2中分别找出一个min和max,然后去较小的min和较大max
f(N)=2f(N/2)+2
=2*(2*f(N/2^2)+2))+2
=2^2*f(N/2^2)+2^2+2
=2^(logN-1)*f(N/2^(logN-1))+2^logN+...+2^2+2
=N/2*f(2)+2*(1-2(logN-1))/(1-2)
=N/2+N-2
=1.5N-2
2.11 寻找最近点对
方法1 穷举 计算两两之间点的距离,比较它们求出最小值,时间复杂度为O(N*N);
方法2:对于数组有序,找最小的差值就容易了,快排的时间复杂度O(nlogn),找最小差值需要O(n);总的时间复杂度为O(nlogn)
方法3:分治的思想,去数组的中间数,将数字分成left和right两部分,分别找出left和right中的最小差值,然后和来自left的一个数和来自right的一个数的差值比较去最小值。时间复杂度O(nlogn)
对于平面二维的情况;将平面上的点映射到x轴上,去这些点的中点将这些点分成left和right两部分,并找出left中的最小值和right中最小值。取MDist=min(MinDist_right,MinDist_Right);取矩形区域MDist*2MDist,比较这个区域中的点中最小距离,因为在这个区域外的点在left和right间的距离肯定大于MDist,每次找两个区域间最短路径的时间消耗为O(N),二分法的时间复杂度为O(logN);总的时间复杂度为O(nlogn).
2.12 快速找满足条件的两个数
方法1:穷举法,计算两两数字之和是否满足条件 时间复杂度为O(n*n)
方法2:对于arr[i],判断另一个数字sum-arr[i]是否在数组中。
对于一个无序的数组查找一个数的复杂度为O(N),对于arr[i]中n个数的查找复杂度O(N*N)
另一个思路是先将数组排序,在利用二分查找复杂度为O(logN);快排的复杂度O(nlogN);总体时间复杂度依然为O(nlogn)
然而,还有一种快速查找hash表,直接可以查找某个数是否存在,查找复杂度为O(1);这样对于N个数总体的复杂度为O(N);
解法三:
先排序,时间复杂度为O(nlogn);在排序的基础上,从前后遍历一次,令i=0;j=n-1;判断arr[i]+arr[j]时候=sum,若相等,则结束,
若小于sum,则i++;若大于sum,则j--;
for(i=0,j=n-1; i<j;){
if(arr[i]+arr[j]==sum){
return (i,j);
}else if(arr[i]+arr[j]<sum){
i++;
}else{
j++;
}
}
return (-1,-1);