编程之美学习心得 二 (未完待续)

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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值