1. 位运算简介
位运算是对二进制数的一种操作,主要以下几种,
(一元)取反(!)、按位或(||)、按位异或(^)、按位与(&&)。
(二元)移位:将一个二进制数中的每一位都向指定方向移动指定位,溢出的部分被舍弃,空缺的部分补充一定值。
这里对移位运算符进行一定的代码解释,方便以后处理这里题目时候翻看:
public static void main(String[] args) {
int a=4;
System.out.println(a+" 向左和右移动:");
System.out.println(a<<2);//a<<2为:a*(2^2),对应二进制数左移两位,缺位正数补0
System.out.println(a>>2);//a>>2为a/(2^2),对应二进制数右移两位,缺位正数补0
int b=-4;
System.out.println(b+" 向左和右移动:");
System.out.println(b<<2);//对于负数而言,缺位补1
System.out.println(b>>2);
//对于>>>,只对32位和64位有意义
System.out.println(a+" 和 "+b+"无符号右移:");
System.out.println(a>>>2);//对于这种符号来说,同样是右移,但是缺位补0,所谓的无符号右移,
System.out.print(b>>>2);// 所以这里的负数一般会变成正数
}
对应的输出为:
基础知识想了解更多,可以参考:https://www.cnblogs.com/SunArmy/p/9837348.html
2. 在题目中的应用
2.1 最大数值
2.1.1 题目分析
不能使用各种比较运算符来找到a和b中最大的那个。
如果我们能得到a-b
的符号位,就可以通过以下函数形式来返回满足条件的数:
//如果a-b>=0,设i=0;否则i=1,这样就可以返回正确的值
return i*b+(i-1)*a;
我们注意到对于二进制数,其首位会显示对应的正负(对于64位long
或者32位int
型来说),这样我们可以对a-b
使用右移的操作,但这里要注意溢出的问题,例如
a
=
2
32
−
1
,
b
=
−
2
32
,
a
−
b
=
2
33
−
1
;
/
/
这
个
时
候
a
−
b
就
会
超
过
32
位
a=2^{32}-1,b=-2^{32},a-b=2^{33}-1;//这个时候a-b就会超过32位
a=232−1,b=−232,a−b=233−1;//这个时候a−b就会超过32位
所以我们可以选择long类型来解决这个问题。这样我们可以通过无符号右移63位来获得符号位。
2.1.2 代码
public static void main(String[] args) {
int a=3,b=6;
int i=(int)(((long)a-(long)b)>>>63);
System.out.println(i);
System.out.print("a和b中最大值为:"+(a*(1-i)+b*i));
}
对应输出为:
2.2 求1+2+…+n
2.2.1 问题分析
解决这道题可以有两种思路:1.短路运算 2.位运算模拟高斯公式
A&&B是具有短路效果的,也就是如果A不成立,B不会执行,所以可以利用这一个特点来进行递归,另外java不认为数字可以充当逻辑值,同时需要有boolean来充当承载,也就是:
public int sumNums(int n) {
boolean b = (n > 0) && ((n += sumNums(n - 1)) > 0);
return n;
}
当n==0时,函数递归就可以结束,开始不断返回值。
我们同时知道:
1
+
2
+
3
+
⋅
⋅
⋅
+
n
=
n
∗
(
n
+
1
)
2
1+2+3+···+n=\frac{n*(n+1)}{2}
1+2+3+⋅⋅⋅+n=2n∗(n+1)
我们可以通过将n+1拆解成2的n次幂数之和,再和n相乘,我们给出示例代码如下:
引用自https://leetcode-cn.com/problems/qiu-12n-lcof/solution/
public int sumNums(int n) {
int n1 = (n & -(n + 1 >> 0 & 1)) << 0;
int n2 = (n & -(n + 1 >> 1 & 1)) << 1;
int n3 = (n & -(n + 1 >> 2 & 1)) << 2;
int n4 = (n & -(n + 1 >> 3 & 1)) << 3;
int n5 = (n & -(n + 1 >> 4 & 1)) << 4;
int n6 = (n & -(n + 1 >> 5 & 1)) << 5;
int n7 = (n & -(n + 1 >> 6 & 1)) << 6;
int n8 = (n & -(n + 1 >> 7 & 1)) << 7;
int n9 = (n & -(n + 1 >> 8 & 1)) << 8;
int n10 = (n & -(n + 1 >> 9 & 1)) << 9;
int n11 = (n & -(n + 1 >> 10 & 1)) << 10;
int n12 = (n & -(n + 1 >> 11 & 1)) << 11;
int n13 = (n & -(n + 1 >> 12 & 1)) << 12;
int n14 = (n & -(n + 1 >> 13 & 1)) << 13;
return ( n1+n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10 + n11 + n12 + n13 + n14) >> 1;
}
这里1<=n<=10000
。所以只用拆解成13次幂足以。
2.2.2 代码分析
这里我们以n=3举例,二进制为11,
//n+1为二进制的100,也就是4,现在需要判断n+1在0-13位上是否有值,所以
n+1>>k&1;//0<=k<=13,1为00000···001,所以这样就可以通过得到的是1还是0来判断k位上是否有值
(n & -(n + 1 >> k & 1))//因为任何整数和-1进行与运算都会得到本身,当n+1在某一位存在值的
//时候,n会得到它本身;不存在值的时候,会得到0
//然后对这个值进行左移,也就是模拟乘积的过程。
写成数学形式就是:
n
∗
(
n
+
1
)
2
=
n
∗
(
f
0
∗
2
0
+
f
1
∗
2
1
+
⋅
⋅
⋅
+
f
k
∗
2
k
)
2
(
其
中
f
k
=
0
o
r
1
)
\frac{n*(n+1)}{2}=\frac{n*(f_0*2^0+f_1*2^1+···+f_k*2^k)}{2}(其中f_k=0or1)
2n∗(n+1)=2n∗(f0∗20+f1∗21+⋅⋅⋅+fk∗2k)(其中fk=0or1)