位运算相关高频面试题

来源:互联网 发布:淘宝宝贝违规被删除 编辑:程序博客网 时间:2024/05/03 09:54

位运算的常见操作符:

按位与  &

按位或  |

按位异或  ^

取反  ~

左移,右侧补0  <<

右移,左侧补符号位  >>

右移,左侧补零  >>>

位运算的面试题大部分靠平时积累,新题在面试场上较难想出解题思路。


布隆过滤器的原理和使用:

网页黑名单系统

垃圾邮件过滤系统

爬虫的网址判断重复系统

容忍一定程度的失误率

对空间要求比较严格(用哈希表或者数据库对数据进行搜索占用空间太大,不符合!!!)

以上情况很可能是考察布隆过滤器的内容!!


布隆过滤器可精确的代表一个集合;

可精确判断某一元素是否在此集合中;

精确程度由用户的具体设计决定;

做到百分之百的精确即正确是不可能的!!!(比如:允许有万分之一的失误率)


布隆过滤器的优势在于,利用很少的空间可以做到精确率较高!!!

布隆过滤器的原理:

k个哈希函数(优秀且彼此相互独立,输出域都大于等于m)

bitarray数组(bit类型的数组,长度为m,每个位置只占一个bit,只有0和1两种状态)


同一个输入对象经过k个哈希函数计算出的结果相互独立!!对算出来的每个结果都对m取余,取余的结果对bitarray相应的位置上置1!

如果某个位置上已经是1了,那么就不需要做什么。

当所有的输入对象都进行了上述过程之后,bitarray中的大部分应该都已经置1了!!这样一个布隆过滤器就生成了!!


检查一个输入是否加入过布隆过滤器:

将这个输入经过上述k个哈希函数进行计算,计算结果对m取余,然后在bitarray中查看相应位置是否都为1,如果有一个不为1,那么这个输入一定没有加入到过布隆过滤器!

但是,如果所有的都是1,那么这个输入就在这个集合中,但是这种情况是有可能发生误判的。



布隆过滤器的bitarray的大小的确定:

如果布隆过滤器的大小相对于样本的数量来说过小的话,那么失误的概率会变大。因为样本数量特别多,

所以有可能大部分的bitarray位置都置1了,所以很容易将一个没有加入过布隆过滤器的输入判定为加入过!!!

因此,布隆过滤器的大小m,是由样本的数量n以及我们想要的失误率p决定的。


设布隆过滤器大小m,样本数量为n,失误率为p,哈希函数数量为k。

那么

m=-(n*ln p)/pow(ln 2, 2),向上取整,之所以向上取整是因为这样可以比预期的失误率更低一些。

k=ln 2*(m/n)

需要注意的是单个样本的大小不影响布隆过滤器的大小,只影响了哈希函数的实现细节。

同时,哈希函数的计算结果应该保证大于m,因为计算出的结果要对m取余。

真正的失误率的计算公式应该是:

pow(1-pow(e,(-n*k/m)),k)


总结生成布隆过滤器的过程:

1. 注意到题目允许有一定程度的失误率。

2. 根据样本个数n,和允许的失误率p, 结合一下公式:

m=-(n*ln p)/pow(ln 2, 2)求出m。(向上取整)

3. 根据已经求得的m,以及一下公式:

k=ln 2*(m/n)=0.7*(m/n);(向上取整)

求得哈希函数个数k。

4. 当然,由于求m的时候可能向上取整了,所以失误率可能不是p,因此可以用下面的公式重新验证

一下p:

pow(1-pow(e,(-n*k/m)),k)



案例一:

如何不用任何额外变量交换两个整数的值?

a=a^b;

b=a^b;

a=a^b;

对于上面的原理的证明如下:

第一步:a=a^b b=b

第二步: b=a^b=a^b^b=a  a=a^b

第三步:a=a^b=a^b^a=b  b=a

所以此时a和b正好交换了。


异或运算的性质:

异或的自反性:a^b^b=a^0=a;

异或的结合律:(a^b)^c=a^(b^c);

异或的交换律:a^b=b^a;

异或运算简单的理解就是不进位的加法,例如:

1+1=0;0+0=0;1+0=1;

特殊性质:x^x=0; x^0=x;x^x^x=x;

实现代码如下:

class Swap {
public:
    vector<int> getSwap(vector<int> num) {
        // write code here
        num[0]=num[0]^num[1];
        num[1]=num[0]^num[1];
        num[0]=num[0]^num[1];
        return num;
    }
};



案例二:给定两个32位整数a和b,返回a和b中较大的。但是不能用任何比较判断!

方法一:得到a-b的符号,根据该符号决定返回a或者b。

下面的程序中,如果n为整数,sign函数返回1;n为复数,sign函数返回0。

public static int flip(int n){

   return n^1;
}

public static int sign(int n){

   return flip((n>>31)&1); 
}

public static int getMax1(int a,int b){

    int c=a-b;

    int scA=sign(c);

    int scB=flip(scA);

    return a*scA+b*scB;//如果c为正数,那么scA就是1;scB就是0!!那么返回的就是a
}

但是上面的方法可能会有问题,当a-b溢出时,会发生错误!!

方法二:根据提前判断两个数符号是否相同来避免溢出的错误判断。符号相同的两个数相减,

结果一定不会溢出;符号不同的数相减,才有可能溢出。因此当符号不同的时候不用a-b的值来判断,而是根据他们的符号来判断:

public static int getMax2(int a,int b){

int c=a-b;

int as=sign(a);//a的符号,as=1表示a为非负数,as=0表示a为负数

int bs=sign(b);//b的符号

int cs=sign(c);//a-b的符号

int difab=as^bs;//表示a和b是否符号不相同,不相同为1,相同为0

int sameab=flip(difab);//表示a和b是否符号相同,相同为1不同为0

int returnA=difab*as+sameab*cs;

int returnB=flip(returnA);

return a*returnA+b*returnB;

}



class Compare {
public:
    int getMax(int a, int b) {
        // write code here
        int c=a-b;
        bool flag=samesign(a,b);
        //如果flag为true表示a和b符号相同,否则他们就是异号的
        //符号不同的数相减的结果有可能溢出
        if(flag){
            return c>0?a:b;
        }else{
            if((a>>(31))==1)
                return b;
            else
                return a;
        }
    }
    
   bool samesign(int a,int b){
       int c=a>>(31);
       int d=b>>(31);
       if(c==d)
           return true;
       else 
           return false;
   }
};

对于上面的实现来说,因为防止a和b相减结果溢出,所以只有在符号相同的时候才用c来比较;否则比较的是它们的符号。

案例三:给定给一个整形数组arr,其中只有一个数出现了奇数次,其他的数都出现了偶数次,请打印这个数。要求时间复杂度为O(N),额外空间复杂度为O(1).

因为 

n^0=n

n^n=0

异或运算满足交换律;

异或运算满足结合律;


所以按照原始的arr数出现的顺序异或的结果,与将arr中出现偶数次的同一个数先异或,再和出现奇数次的数

进行异或的结果是相同的。


所以令E=0,然后将E和arr中的所有数依次异或,最后的结果就是出现奇数次的那个数。

class OddAppearance {
public:
    int findOdd(vector<int> A, int n) {
        // write code here
        int eo=0;
        for(int i=0;i<n;i++){
            eo=eo^A[i];
        }
        return eo;
    }
};


案例四:给定给一个整型数组arr,其中有两个数出现了奇数次,其他的数都出现了偶数次,打印这两个数。

要求时间复杂度为O(N),额外空间复杂度为O(1)。

1. 整数n与0的异或结果为n。

2, 整数n与自己异或结果为0。

eo=0,arr中a和b出现了奇数次,剩下的数都出现了偶数次。

eo与arr中所有的数异或完成后,eo=a^b,因为a和b为不同的数,所以eo不为0,。

eo中必定有一位为1,假设第k为为1的话,那么说明a和b的第k位一定不一样。

设置新整数eo'=0.

eo'与arr中第k为为1的那些整数进行异或,异或完成后,eo'为a或b中的一个,

另一个数等于eo'^eo。



class OddAppearance {
public:
    vector<int> findOdds(vector<int> arr, int n) {
        // write code here
        vector<int> result;
        int eo=0;
        for(int i=0;i<n;i++){
            eo=eo^arr[i];
        }
        int temp=eo;
        //下面应该找到eo中为1的某一位
        int count=0;
        while(eo!=0){
            if(eo%2==1)
                break; 
            eo=eo>>(1);
            count=count+1;         
        }//也就是说从右边数第count+1位是1,count表示需要移动的次数
        //下面就要找到arr中从右边数第count+1位是1的数
        int eo1=0;
        for(int j=0;j<n;j++){
            if((arr[j]>>(count))%2==1)
                eo1=eo1^arr[j];
        }
        //eo1代表的是其中一个奇数
        eo=temp^eo1;
        //eo代表的是另一个奇数
        if(eo>eo1){
            result.push_back(eo1);
            result.push_back(eo);
        }else{
            result.push_back(eo);
            result.push_back(eo1);
        }
        return result;
           


    }
};

对于上面的实现方式来说,找为1的位比较麻烦,为了简化算法,可以参考下面的实现方式:

public class OddAppearance {public int[] findOdds(int[] arr, int n) {int eO = 0, eOhasOne = 0;for (int curNum : arr) {eO ^= curNum;}int rightOne = eO & (~eO + 1);for (int cur : arr) {if ((cur & rightOne) != 0) {eOhasOne ^= cur;}}int small = Math.min(eOhasOne, (eO ^ eOhasOne));int big = Math.max(eOhasOne, (eO ^ eOhasOne));return new int[] { small, big };}}
上面的方式中,
eO & (~eO + 1)  左边的表达式求的就是eo的从最右边开始的第一个为1的位m为1,其他位都是0的数。这样的话,别的数和这个数与操作之后,凡是不为0的,那么它们从右边数第m位一定都是1!!!


案例五:请设置一种加密过程,完成对明文text的加密和解密工作。

异或运算可完成简单的加密与解密过程。

明文text,用户给定的密码pw,假设密文为cipher,

cipher=text^pw

text=cipher^pw=(text^pw)^pw=text^(pw^pw)=text。

如果text长度大于pw,循环使用pw与text进行按位异或。

原创粉丝点击