一、不用额外变量交换两个整数的值

  如果给定整数a和b,用以下三行代码即可交换a和b的值。a = a ^ b; b = a ^ b; a = a ^ b;

a = a ^ b :假设a异或b的结果记为c,c就是a整数位信息和b整数位信息的所有不同信息。例如,a=4=100,b=3=011,a^b=c=000
b = a ^ b :a异或c的结果就是b。比如a=4=100,c=000,a^c=011=3=b,也就是b = a ^ b ^ b = a
a = a ^ b :b异或c的结果就是a。比如b=3=011,c=000,b^c=100=4=a,也就是a = a ^ b ^ a =

  

  二、不用任何比较判断找出两个数中较大的数

  问题:给定两个32位整数a和b,返回a和b中较大的。

  1.得到a-b的值的符号,如果a-b的值出现溢出,返回结果就不正确

  sign函数返回整数n的符号,整数和0返回1,负数返回0。如果a-b的结果为0或整数,那么scA=1,scB=0,return a ;如果a-b的值为负数,那么scA=0,scB=1,return b;

public int flip(int n){
return n ^ 1;
} public int sign(int n){
return flip((n>>31)&1);
} public int getMax1(int a, int b){
int c = a - b;
int scA = sign(c);
int scB = flip(scA);
return a * scA + b * scB;
}

  2.彻底解决溢出的问题

  情况1:如果a和b的符号不同(disSab == 1,sameSab==0),则有

    如果a为0或正,b为负(sa == 1,sb == 0),那么returnA与sc无关,为sa==1,returnB=0,返回a

    如果a为负,b为0或正(sa==0,sb==1),那么returnA==0,returnB=1,返回b

  情况2:如果a和b的符号相同(difSab==0,sameSab=1),那么此时a-b的值绝对不会溢出:

    如果a-b为0或正(sc==1),那么returnA=sc=1,returnB=0,返回a

    如果a-b为负(sc==0),那么returnA=0,returnB=1,返回b

public int getMax2(int a, int b){
int c = a - b;
int sa = sign(a);
int sb = sign(b);
int sc = sign(c);
int difSab = sa ^ sb;
int sameSab = flip(difSab);
int returnA = difSab * sa + sameSab * sc;
int returnB = fiip(returnA);
return a * returnA + b * return B;
}

  

  三、整数的二进制表达式中有多少个1

  问题:给定一个32位整数n,可为0,可为正,可为负,返回该整数二进制表达式中1的个数

  1.整数n每次进行无符号右移(>>>)一位,检查最右边的bit是否为1。需要经过32次循环

public int count1(int n){
int res = 0;
while(n!=0){
res += n & 1;
n >>> = 1;
}
}

  2.循环次数只和1的个数有关的解法。每进行一次n &= (n-1);操作,接下来在while循环中就可以忽略掉bit位上为0的部分。

  例如,n=01000100,n-1=01000011,n&(n-1)=01000000,res=1,然后,n=01000000,n-1=00111111,n&(n-1)=00000000,res=2,结束。

  因此,n&(n-1)操作实际上是抹掉n最右边的那一个1。

public int count2(int n){
int res = 0;
while(n != 0){
n &= (n-1);
res++;
}
return res;
}

  3.同方法2,只不过是将n&(n-1)操作改成n -= n & (~n+1),也是移除最右侧的1的过程。n & (~n+1)是得到n中最右侧的1

  例如:n=01000100,~n=10111011,~n+1=10111100,n & (~n+1) = 00000100,n - n & (~n+1) = 01000100,同理。

  

  四、在其他数都出现偶数次的数组中找到出现奇数次的数

  问题一:只有一个数出现了奇数次,其他的数都出现了偶数次

public void printOddTmesNum1(int[] arr){
int eO = 0;
for(int cur : arr){
eO ^= cur;
}
System.out.println(eO);
}

  问题二:有两个数出现了奇数次,其他的数出现了偶数次

  主要关注:int rightOne = eO & (~eO + 1);这个操作是得到eO最右边的1表示的数,例如01000100经过操作后变成00000100

public void printOddTimesNum2(int[] arr){
int eO = 0, eOhasOne = 0;
for(int curNum : arr){
eO ^= curNum;
}
int rightOne = eO & (~eO + 1);
for(int cur : arr){
if((cur & right) != 0){
eOhasOne ^= cur;
}
}
System.out.println(eOhasOne + " " + (eO ^ eOhasOne));
}

  五、在其他数都出现k次的数组中找到只出现一次的数

  问题:给定一个整型数组arr和一个大于1的整数k,已知arr中只有1个数出现了1次,其他的数都出现了k次,返回只出现1次的数

两个七进制的数,忽略进位相加:
a : 6 4 3 2 6 0 1
b : 3 4 5 0 1 1 1
c : 2 1 1 2 0 1 2

  思路:上面的计算中,第i位上无进位相加的结果就是c[i] = (a[i] + b[i])%7。同理,k进制的两个数a和b,在第i位上相加的结果就是c[i] = (a[i] + b[i])%k。那么,如果k个相同的k进制数进行无进位相加,根据c[i] = (k * a[i] + k * b[i])%k,可知,相加的结果一定是每一位上都是0的k进制数。

  解法:设置一个变量eO,它是一个32位的k进制数,且每个位置上都是0。然后遍历arr,把遍历到的每一个整数都转换为k进制数,然后与e0进行无进位相加。遍历结束后,把32位的k进制数eORes转换成十进制就是要求的结果。

  函数1:将十进制的数转换成32位k进制的数组

public int[] getKSysNumFromNum(int value, int k){
int[] res = new int[32];
int index = 0;
while(value != 0){
res[index++] = value % k;
value = value / k;
}
return res;
}

  函数2:将表示k进制的数组转换成十进制的数

public int getNumFromKSysNum(int[] eO, int k){
int res = 0;
for(int i = eO.length - 1 ; i != -1; i--){
res = res * k + eO[i];
}
return res;
}

  函数3:将十进制的value转换成curKSysNum数组表示的32位k进制数后无进位地加到eO数组的每一位上

public void setExclusiveOf(int[] eO, int value, int k){
int[] curKSysNum = getKSysNumFromNum(value, k);
for(int i = 0; i != eO.length; i++){
eO[i] = (eO[i] + curKSysNum[i]) % k;
}
}

  函数4,将arr中所有的数转换成32位k进制后加到eO变量的每一位上,然后将eO变量转换成十进制的数并返回

public int onceNum(int[] arr, int k){
int[] eO = new int[32];
for(int i = 0; i != arr.length; i++){
setExclusiveOr(eO, arr[i], k);
}
int res = getNumFromKSysNum(eO, k);
return res;
}

  六、只用位运算不用算术运算实现整数的加减乘除运算

  题目:给定两个32位整数a和b,可正,可负,可0。不能使用算术运算符,分别实现a和b的加减乘除运算。如果给定的a和b执行加减乘除的某些结果本来就会导致数据的溢出,那么不用为那些结果负责。

  1.用位运算实现加法运算

  注意:初始化sum=a,是为了考虑当b为0时,无法进入while循环执行sum = a ^ b;这个操作。

public int add(int a, int b){
int sum = a;
while( b != 0){
sum = a ^ b;
b = (a & b) << 1;
a = sum;
}
return sum;
}

  分析实现过程:

1.如果不考虑进位,a^b就是正确结果,因为1加1=0,1加0=1,0加1=1,0+0=0
例如:
a:0 0 1 0 1 0 1 0 1
b:0 0 0 1 0 1 1 1 1
c:0 0 1 1 1 1 0 1 0
2.在只算进位的情况下,也就是a加b过程中由于进位产生的值是什么,就是(a&b)<<1,因为在第i位上只有1和1相加才会产生上一位即i-1位的进位
a:0 0 1 0 1 0 1 0 1
b:0 0 0 1 0 1 1 1 1
d:0 0 0 0 0 1 0 1 0(从右数第1位和第3位需要进位,因此在相加的过程中,第2位和第4位上需要加上1,因此(a&b)<<1)
3.把第1步的不考虑进位的相加值与第2步的只考虑进位的产生值再相加,就是最终的结果。由于过程中可能还会产生进位,所以需要重复直到进位产生的值完全消失。
a:0 0 1 0 1 0 1 0 1
b:0 0 0 1 0 1 1 1 1
c:0 0 1 1 1 1 0 1 0
d:0 0 0 0 0 1 0 1 0 c:0 0 1 1 1 0 0 0 0
d:0 0 0 0 1 0 1 0 0 c:0 0 1 1 0 0 1 0 0
d:0 0 0 1 0 0 0 0 0 c:0 0 1 0 0 0 1 0 0
d:0 0 1 0 0 0 0 0 0 c:0 0 0 0 0 0 1 0 0
d:0 1 0 0 0 0 0 0 0 c:0 1 0 0 0 0 1 0 0(返回)
d:0 0 0 0 0 0 0 0 0

  2.用位运算实现减法运算

  实现a-b,只要实现a+(-b)即可。一个数的相反数,就是这个数的二进制数表达取反加1(补码)。

public int negNum(int n){
return add(~n, 1);
} public int minus(int a, int b){
return add(a, negNum(b));
}

  3.用位运算实现乘法运算

  a*b=a * 20 * b0 + a * 21 * b1 + a * 22 * b2 + ... + a * 231 * b31(bi表示的是二进制中第i位的值,从左起0开始)

public int multi(int a, int b){
int res = 0;
while(b != 0){
if((b & 1) != 0){
res = add(res, a);
}
a <<= 1;
b >>> = 1;
return res;
}

  分析执行过程:

假设a=22=000010110,b=13=000001101,res=0
a:0 0 0 0 1 0 1 1 0
b:0 0 0 0 0 1 1 0 1
r:0 0 0 0 0 0 0 0 0
b的最右侧是1,所以res = res + a,同时b右移一位,a左移一位
a:0 0 0 1 0 1 1 0 0
b:0 0 0 0 0 0 1 1 0
r:0 0 0 0 1 0 1 1 0
b的最右侧是0,res不变,同时b右移一位,a左移一位
a:0 0 1 0 1 1 0 0 0
b:0 0 0 0 0 0 0 1 1
r:0 0 0 0 1 0 1 1 0
b的最右侧是1,res = res + a,同时b右移一位,a左移一位
a:0 1 0 1 1 0 0 0 0
b:0 0 0 0 0 0 0 0 1
r:0 0 1 1 0 1 1 1 0
b的最右侧是1,res = res + a,同时b右移一位,a左移一位
a:1 0 1 1 0 0 0 0 0
b:0 0 0 0 0 0 0 0 0
r:1 0 0 0 1 1 1 1 0
b为0,返回res=100011110=286

  4.用位运算实现除法运算

  用位运算实现除法运算,其实就是乘法的逆运算。

  (1)a和b都不为负数或者如果a和b中有一个负数或者都为负数时,可以先把a和b转成正数,计算完成后再看res的真实符号即可(正负得负、负负得正、正正得正)。

public boolean isNeg(int n){
return n < 0;
} public int div(int a, int b){
int x = isNeg(a) ? negNum(a) : a;
int y = isNeg(b) ? negNum(b) : b;
int res = 0;
for(int i = 31; i > -1; i = minus(i,1){
if(x >= (y << i)){
res |= (1<<i);
x = minus(x, y<<i);
}
}
return isNeg(a) ^ isNeg(b) ? negNum(res) : res;
}

  如果b*res=a,那么a=b * 20 * res0 + b * 21 * res1 + b * 22 * res2 + ... + b * 231 * res31

  分析执行过程:让b向左移动i次,即b * 2i,然后观察a是否b * 2i,如果大于,就令res的第i位等于1,然后让a - b * 2i为a,然后反复操作。

假设a=286=100011110,b=22=000010110,res=0
a:1 0 0 0 1 1 1 1 0
b:0 0 0 0 1 0 1 1 0
r:0 0 0 0 0 0 0 0 0
(i=3时)a = a - b * 2

3

a:0 0 1 1 0 1 1 1 0
b:0 0 0 0 1 0 1 1 0
r:0 0 0 0 0 1 0 0 0
(i=2时)a = a - b * 2

2

(i=2时)
a:0 0 0 0 1 0 1 1 0
b:0 0 0 0 1 0 1 1 0
r:0 0 0 0 0 1 1 0 0
(i=1时)b向左移动一位后大于a,说明a已经不能包含b * 2

1

,
a:0 0 0 0 1 0 1 1 0
b:0 0 0 0 1 0 1 1 0
r:0 0 0 0 0 1 1 0 1
(i=0时)b向左移动一位后a==b,说明剩下的a还能包含一个b * 2

0

,即res0=1,此时说明a已经被完全分解干净,返回res=000001101=13

  (2)以上方法可以算绝大多数情况,但是int类型的整数最小值为-2147483648,最大值为2147483647,最小值的绝对值比最大值的绝对值大1,所以,如果a或b等于最小值,是转不成相对应的正数的(~n + 1)。

  即有下面四种情况:

  • 如果a和b都不为最小值,直接使用div(a,b)
  • 如果a和b都为最小值,直接返回1
  • 如果a不为最小值,而b为最小值,直接返回0
  • 如果a为最小值,b不为最小值,怎么办?

  假设整数的最大值为9,最小值为-10,当a和b都属于[-9,9]时,也就是情况1;当a和b都等于-10时,也就是情况2;当a属于[-9,9],而b等于-10时,也就是情况3;

  那么,当a=-10,而b属于[-9,9]时,

  第一步:假设a=-10,b=5

  第二步:计算(a+1)/b的结果,记为c,即c=-9/5=-1

  第三步:计算c*b的结果,即-1*5=-5

  第四步:计算(a - (c * b))/b,记为rest,意义是修正值,即(-10 - (-5))/5=-1,得到的是修正值,即rest=-1

  第五步:返回c+rest,即-9

  即a/b的值可以表示为

  综上,除法运算的全部过程为:(注意要有异常处理的过程。)

public int divide(int a, int b){
if(b==0){
throw new RuntimeException("divided is 0");
}
if(a == Integer.MIN_VALUE && b == Integer.MIN_VALUE){
return 1;
} else if(b == Integer.MIN_VALUE){
return 0;
} else if(a == Integer.MIN_VALUE){
int res = div(add(a,1),b);
return add(res, div(minus(res, b)), b));
} else{
return div(a, b);
}
}

  七、O(n)时间复杂度得到输入数组中某两个数异或的最大值。

  例如:[3, 10, 5, 25, 2, 8]中,5^25的最大值是28

  思路:比特位操作。

  解法:

    生成变量max,表示

    生成变量mask,表示,

    XOR性质,A^B=C → A^B^B=C^B → A=C^B 则tmp ^ prefix = max →

3  →  0 0 0 1 1
10 → 0 1 0 1 0
5 → 0 0 1 0 1
25 → 1 1 0 0 1
2 → 0 0 0 1 0
8 → 0 1 0 0 0 i=4时,mask=10000, set={00000,10000},tmp=10000,prefix=00000, max=10000
i=3时,mask=11000, set={00000,01000,11000},tmp=11000,prefix=00000,max=11000
i=2时,mask=11100, set={00000,01000,00100,11000},tmp=11100,prefix=00100,max=11100
i=1时,mask=11110, set={00010,01010,00100,11000,01000},tmp=11110,set中不包含
i=0时,mask=11111, set={00011,01010,00101,11001,00010,01000},tmp=11111,set不包含

OptimalSolution(8)--位运算的更多相关文章

  1. Java 位运算2-LeetCode 201 Bitwise AND of Numbers Range

    在Java位运算总结-leetcode题目博文中总结了Java提供的按位运算操作符,今天又碰到LeetCode中一道按位操作的题目 Given a range [m, n] where 0 <= ...

  2. 简简单单学会C#位运算

    一.理解位运算 要学会位运算,首先要清楚什么是位运算?程序中的所有内容在计算机内存中都是以二进制的形式储存的(即:0或1),位运算就是直接对在内存中的二进制数的每位进行运算操作 二.理解数字进制 上面 ...

  3. SQL Server时间粒度系列----第8节位运算以及设置日历数据表节假日标志详解

    本文目录列表: 1.位运算 2.设置日历数据表节假日标志 3.总结语 4.参考清单列表   位运算   SQL Server支持的按位运算符有三个,分别为:按位与(&).按位或(|).按位异或 ...

  4. js中的位运算

    按位运算符是把操作数看作一系列单独的位,而不是一个数字值.所以在这之前,不得不提到什么是"位": 数值或字符在内存内都是被存储为0和 1的序列,每个0和1被称之为1个位,比如说10 ...

  5. Java中的位运算

    昨天去面试的时候做到了一道Java的位运算题目,发现有个运算符不懂:">>>",今天特地查了一下,并小结一下常见的位运算符号: ~  按位非(NOT)(一元运算) ...

  6. C#位运算讲解与示例

    首先每一个权限数都是2的N次方数 如:k1=2 ; //添加 k2=4 ; //删除 k3=8; //修改 ... 如此定义功能权限数,当需要组合权限时,就需要对各个所拥有的权限数按位或了. 如: p ...

  7. C#枚举中的位运算权限分配浅谈

    常用的位运算主要有与(&), 或(|)和非(~), 比如: 1 & 0 = 0, 1 | 0 = 1, ~1 = 0 在设计权限时, 我们可以把权限管理操作转换为C#位运算来处理. 第 ...

  8. Java位运算经典实例

    一 源码.反码.补码 正数的源码.反码.补码相同,例如5:            5的源码:101            5的反码:101            5的补码:101 负数的源码.反码.补 ...

  9. C入门---位运算

    程序中的所有数在计算机内存中都是以二进制的形式储存的.位运算直接对整数在内存中的二进制位进行操作.由于位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快. (1),与(&)运算 ...

随机推荐

  1. 重学JavaScript之面向对象的程序设计(继承)

    1. 继承 ES 中只支持实现继承,而且其实现继承主要依靠原型链来实现的. 2. 原型链 ES中 描述了 原型链的概念,并将原型链作为实现继承的主要方法.其基本思想是利用原型让一个引用类型继承另一个引 ...

  2. Mycat 配置文件schema.xml

    1.介绍 schema.xml 作为 MyCat 中重要的配置文件之一,管理着 MyCat 的逻辑库.表.分片规则. DataNode 以及 DataSource. 2.schema相关标签 sche ...

  3. java面向对象,数据类型深入

    java程序员面试题day02 1.类和对象有什么区别? java中的类通过class关键字进行定义,代表的是一种抽象的集合,在类中可以定义各种的属性和方法,代表的是每一个类的实例的特定的数据和动作, ...

  4. Spark 学习笔记之 map/flatMap/filter/mapPartitions/mapPartitionsWithIndex/sample

    map/flatMap/filter/mapPartitions/mapPartitionsWithIndex/sample:

  5. Java 学习笔记之 方法内的临时变量是线程安全

    方法内的临时变量是线程安全: 方法内部的私有变量,是线程安全的. public class HasSelfPrivateNum { public void addI(String username) ...

  6. Docker5-docker私库的搭建及常用方法-harbor-registry方式

    一.简介 1.官方已经提供registry镜像为什么还需要用harbor 1)registry缺少镜像清理机制,可以push但是不能删除,耗费空间 2)registry缺乏相应的扩展机制 3)harb ...

  7. IDEA统一注释格式(干货)

    统一注释格式: Idea自动生成注释: /** *@author 你的名字的简写 *@date ${DATE}${TIME} 在每个功能上添加注释,例子如下: 我在Mapper文件中添加了自己书写的S ...

  8. 网络下载器 Internet Download Manager v6.35.5 绿色便携版

    Internet Download Manager,简称 IDM,是国外的一款优秀网络下载工具.目前凭借着下载计算的速度优势在外媒网站中均受好评,现在已被多数国人熟知.Internet Downloa ...

  9. 52个有效方法(1) - 了解Objective-C语言的起源

    Objective-C语言使用的是"消息结构"而非"函数调用" "消息结构"和"函数调用"之间的区别 "消息结 ...

  10. 同步与互斥_percpu变量

    percpu变量的关键就是:要求根据CPU的个数,在内存中生成多份拷贝,并且能够根据变量名和CPU编号,正确的对各个CPU的变量进行寻址. 采用per-cpu变量有下列好处:所需数据很可能存在于处理器 ...