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);