部分转载自 “小浩算法”微信公粽号!真心良心的公众号!
目录
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;
}