位运算-不用符号/if/while计算-算法题-整理

https://mmbiz.qpic.cn/mmbiz_png/cXoa07I9qrkUjIictMgFqhqd75N6WHCzCoOzibOiazNsg4e5jBsZGC3vZUL7zx29x1zfFVuCgKRib28SllE4pxxUyw/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

部分转载自 “小浩算法”微信公粽号!真心良心的公众号!

目录

1、位运算

1、两数求和

2、n-1个数里包含0-n-1里的n-1个,缺哪个?

3、一个数出现一次,其余都2次。找到这个数

4、两个数出现一次,其余都2次。找到这两个数

4、一个数出现一/两次,其余都3次。找到这个数

5、1的个数(汉明重量)

6、是不是2的幂

7、是不是4的幂

8、是不是3的幂

2、不用符号


1、位运算

1、两数求和

a^b是求和得到的不进位的值,a&b得到的是求和的进位

1^1=0 0^0=0 0^1=1 1^0=1            1&1=1 1&0=0 0&1=0 0&0=0

因此 a+b  = 求和得到的不进位的值+(求和的进位)左移动一位,但是这个求和也会有进位!所以要循环求和,一直到没有进位为止

#include <bits/stdc++.h>
using namespace std;
void main(){
    int a,b; cin>>a>>b;
    while(b){
        int temp=a^b;
        b=(a&b)<<1;
        a=temp;
    }
    cout<<a;
}

2、n-1个数里包含0-n-1里的n-1个,缺哪个?

方法1: 等差数列求和,和减去当前序列的和

方法2: 位运算,将序列里的数异或一遍,之后再和0-n都异或一遍,两个相同的数,使用异或可以相消除

方法3: 遇到的那个数所在的位的值+n+2,最后小于等于n的那位就是缺的那个

#include <numeric>
void main(){
    int a,n; cin>>a>>n;//输入a和n
    vector<int> now;
    for(int i=0;i<n;i++){
        if(i!=a) now.push_back(i);
    }
    //方法1
    cout<<n*(n-1)/2-accumulate(now.begin(),now.end(),0)<<endl;
    //方法2
    int ans=0;
    for(int i=0;i<n;i++) ans^=i;
    for(int i=0;i<n-1;i++) ans^=now[i];
    cout<<ans<<endl;
    //方法3
    for(int i=0;i<n-1;i++) now[now[i]]=now[now[i]%(n+2)]+n+2;
    cout<<distance( now.begin(), min_element(now.begin(),now.end()) );
}

3、一个数出现一次,其余都2次。找到这个数

相同的两个数异或为0,都异或一次,之后得到的就是这个数

void main(){
    int a[11]={2,3,4,9,9,1,3,2,5,5,1};
    int ans=0;
    for(int i=0;i<11;i++)
        ans^=a[i];
    cout<<ans<<endl;
}

4、两个数出现一次,其余都2次。找到这两个数

先都异或一遍, 得到目标两个数的异或结果。两个不一样的数异或之后一定有的位是1,找这个位来给原来的数组分为2组。之后在每一组内异或,得到每一个组内那个单独出现一次的数。

void main_2_2() {
    int a[12] = {2, 3, 4, 10,9, 9, 1, 3, 2, 5, 5, 1};
    int ans=0;
    for(int i=0;i<12;i++)
        ans^=a[i];
    int now=1,ansnow=0;
    while(1) {//两个不一样的数异或之后一定有的位是1,找这个位来给原来的数组分为2组。
        ansnow=ans&now;
        if(ansnow) break; //此时now就是所求的两个不一样的数里面,不一样那位。
        now=now<<1;//注意不要忘记赋值
    }
    int a1=0,a2=0;
    for(int i=0;i<12;i++)
    {
        if((a[i]&now)==0) a1^=a[i];//   注意不要忘记括号!
        else a2^=a[i];
    }
    cout<<a1<<" "<<a2;
}

4、一个数出现一/两次,其余都3次。找到这个数

方法1: (都三次sum-现在sum)/2

方法2: 统计数组中所有元素的每一位,若为3的倍数,所求数的该二进制位对3取余为0,否则为1

对于“每个其余元素,均出现了二次”之所以可以使用“异或”进行求解,原因是因为“异或”操作可以让同位有两个1就归于0,有两个1就归于0,可以看作一个取余的过程,看每一位1的个数,之后取余。得到的就是这个数异或之后的结果。那对于其余元素出现三次的,是不是只要可以让其三者相同归 0,也就是每一位有3个1就归0,也看每一位上的1的个数,之后取余。那么很明显,一个数出现3次,那么就归0了,刚刚题目里说里,只有一个数出现1次,所以最终每一位相加不会出现2的情况。

方法3: 状态机 从出现一次的状态-出现2次-出现3次

void main_3_1(){
    int a[16]={1,1,1,20,20,20,3,3,3,4,4,4,9,6,6,6};

    //方法1
    vector<int> aa(a, a+sizeof(a)/sizeof(int));
    set<int> seta(aa.begin(),aa.end());
    aa.assign(seta.begin(),seta.end());
    //for(int i=0;i<aa.size();i++) cout<<aa[i]<<" ";
    cout<<(3*accumulate(aa.begin(),aa.end(),0)-accumulate(a,a+16,0))/2<<endl;

    //方法2:统计数组中所有元素的每一位,若为3的倍数,所求数的该二进制位对3取余为0,否则为1。
    int result=0;
    for(int i=0;i<32;i++)//我们统计32位数,因为正常c++里int类型是32位
    {
        int bits=0;
        for(int j=0;j<16;j++)
            bits+=(a[j]>>i)&1;//移位!
        result|=(bits%3)<<i;//最终得到的bits就是第i位有多少个1,%3就是看第i位是0/1,最终这个0/1放回第i 位用或
    }
    cout<<result<<endl;

    //方法3:3进制的状态机
    int m=0,n=0;
    for(int i=0;i<16;i++){
        m=(m^a[i]) & ~n;
        n=(n^a[i]) & ~m;
    }
    cout<< m;
}

如果题目改为:一个数出现两次,其余都3次。找到这个数。主要方法改动在方法2里,输出的不是m而是n了。

void main_3_2(){
    int a[17]={1,1,1,19,20,19,20,3,3,3,4,4,4,19,6,6,6};
    //方法1
    vector<int> aa(a, a+sizeof(a)/sizeof(int));
    set<int> seta(aa.begin(),aa.end());
    aa.assign(seta.begin(),seta.end());
    //for(int i=0;i<aa.size();i++) cout<<aa[i]<<" ";
    cout<<(3*accumulate(aa.begin(),aa.end(),0)-accumulate(a,a+17,0))<<endl;
    //方法2:统计数组中所有元素的每一位,若为3的倍数,所求数的该二进制位对3取余为0,否则为1。
    int result=0;
    for(int i=0;i<32;i++)//我们统计32位数,因为正常c++里int类型是32位
    {
        int bits=0;
        for(int j=0;j<17;j++)
            bits+=(a[j]>>i)&1;//移位!
        result|=((bits%3)/2)<<i;//最终得到的bits就是第i位有多少个1,%3就是看第i位是0/1,最终这个0/1放回第i 位用或
    }
    cout<<result<<endl;
    //方法3:3进制的状态机
    int m=0,n=0;
    for(int i=0;i<17;i++){
        m=(m^a[i]) & ~n;
        n=(n^a[i]) & ~m;
    }
    cout<<n;
}

5、1的个数(汉明重量)

方法1:遍历每一位看看是不是1,如果是1就记录下来

方法2:利用n&(n-1)是把n当前最右边1变为0,一直到最后1都变为0,结束,之后看能变几次。

void main_count_1(){
    unsigned long long aa;cin>>aa;
    //方法1
    int a=aa;
    int count1=0;
    while(a){
        if((a&1)!=0) count1++;
        a=a>>1;
    }
    cout<<count1<<endl;
    //方法2:
    int count2=0;
    int n=aa;
    while(n){
        count2++;
        n=n&(n-1);//这是相当于去掉最右边那个1。
    }
    cout<<count2<<endl;
};

6、是不是2的幂

利用n&(n-1)是把n当前最右边1变为0,如果是2的幂最右边变0之后就是0了。

void main_2_mi(){
    int a;cin>>a;
    if(a&(a-1)) cout<<false;
    else cout<<true;//log(a)/log(2)
}

7、是不是4的幂

4的幂,首先判断是不是2的幂,因为4的幂也是就1位是1。之后是4的幂与不是的差别4的倍数奇数位是1.0x55555555奇数位都是1,所以就看这个数与0x55555555与的结果,不是0,就是4的幂。
void main_4_mi(){
    int a;cin>>a;
    if( !(a&(a-1)) && (0x55555555 & a)) cout<<true;//0x55555555奇数位都是1,而4的倍数奇数位是1
    else cout<<false;
}

8、是不是3的幂

方法1:若n是3的幂,那么log3(n)一定是个整数,由换底公式可以的得到log3(n) = log10(n) / log10(3),只需要判断log3(n)是不是整数

方法2:假设一个数Num是3的幂,那么所有Num的约数都是3的幂,如果一个数n小于Num且是3的幂,那么这个数n一定是Num的约数。我们只需要找到一个最大的3的幂,看看参数n是不是此最大的幂的约数就行。我们可以知道最大的int类型的数是INT_MAX,那么将他做log3(INT_MAX)向下去整,就可以得到最大的整数n,使得3的n次幂是可以表示的最大的3次幂。之后看n是不是这个数的约数。

void main_3_mi(){
    double epsilon=1e-6;
    int a;cin>>a;
    //方法1:如果一个数是3的幂,那么log3(n)一定是整数,在计算机里,log默认是10为底的,所以log3(n)=log10(n)/log10(3)
    double res=log(a)/log(3);
    cout<<(abs(res-int(res))<= epsilon)<<endl; //cout<< (res-int(res)==0)? true: false; 容易出现精度错误
    //方法2:
    cout<<(int(pow(3,int(log(INT_MAX)/log(3))))%a==0)<<endl;
}

总结?

 

1、使用 x & 1 == 1 判断奇偶数。(注意,一些编辑器底层会把用%判断奇偶数的代码,自动优化成位运算)

2、不使用第三个数,交换两个数。x = x ^ y , y = x ^ y , x = x ^ y。(早些年喜欢问到,现在如果谁再问,大家会觉得很low)

3、两个相同的数异或的结果是 0,一个数和 0 异或的结果是它本身。(对于找数这块,异或往往有一些别样的用处。)

4、x & (x - 1) ,可以将最右边的 1 设置为 0。(这个技巧可以用来检测 2的幂,或者检测一个整数二进制中 1 的个数,又或者别人问你一个数变成另一个数其中改变了多少个bit位,统统都是它)

5、i+(~i)=-1,i 取反再与 i 相加,相当于把所有二进制位设为1,其十进制结果为-1。

6、对于int32而言,使用 n >> 31取得 n 的正负号。并且可以通过 (n ^ (n >> 31)) - (n >> 31) 来得到绝对值。(n为正,n >> 31 的所有位等于0。若n为负数,n >> 31 的所有位等于1,其值等于-1)

7、使用 (x ^ y) >= 0 来判断符号是否相同。(如果两个数都是正数,则二进制的第一位均为0,x^y=0;如果两个数都是负数,则二进制的第一位均为1;x^y=0 如果两个数符号相反,则二进制的第一位相反,x^y=1。有0的情况例外,^相同得0,不同得1)

2、不用符号

不使用除号取模乘号实现两数相除

用被除数减去除数,减尽为止,可以优化:每次减的可以不是被除数,而是被除数的倍数。

//忘记考虑溢出问题!!!How can I forget???!!!
	int divide(int dividend, int divisor) 
	{
		long long a = dividend;
		long long b = divisor;
		a = abs(a); b = abs(b);
		int res = 0;
		while (a>=b)
		{
			long long t = b;
			for (int i = 1; a >= t; i <<= 1, t <<= 1)
			{
				a -= t;
				res += i;
			}
		}
		return ((dividend<0)^(divisor<0))? -res:res;
	}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值