一、位运算
所有运算到计算中底层中都会变成位运算,位运算可以提高程序的效率,而且我们在研究JDK或者某个框架的源码时,会发现很多地方都用到了位运算!
异或运算
-
描述:对应位相同为
0
,不同为1
,在实际运算的时候可以理解为**无进位相加** -
性质:
0^N=N
,N^N=0
- 异或运算满足交换律和结合律:
a^b=b^a
,(a^b)^c=a^(b^c)
- 由性质2推出:一批数统一异或在一起得到一个值,和异或的先后顺序无关
-
应用:
-
使用异或运算**交换两个数的值**
int a = 甲, b = 乙 a = a ^ b ---> a = 甲^乙 b = a ^ b ---> b = 甲^乙^乙 = 甲^0 = 甲 a = a ^ b ---> a = 甲^乙^甲 = 甲^甲^乙 = 0^乙 = 乙
优点:不用申请额外的空间,即可完成交换;位运算效率更高
【注意】:两个变量所指向的内存必须不相同
【面试题】一个int类型的一维数组arr
①其中有一种数出现了奇数次,其他数都出现了偶数次,找出该数int eor = 0 eor^arr[0]^arr[1]^...^arr[N-1]最终的结果即为该数
②其中有两种数出现了奇数次,其他数都出现了偶数次,找出这两个数
假设这两个数分别为a、b int eor = 0 eor = eor^arr[0]^arr[1]^...^arr[N-1] = a^b 由于a≠b,所以eor = a^b ≠ 0 eor等于1的位置,a和b在该位置一定不相同,假设该位置为第8位(只用考虑一个为1的位置) 再进行一次异或遍历: int eor' = 0 只将第8位为1的数arr[i]与eor'异或,最终就会得到a/b
Java实现:
public class EvenTimesOddTimes { /** * 【面试题目】一个int类型的一维数组arr * ①其中有一种数出现了奇数次,其他数都出现了偶数次,找出该数 * ②其中有两种数出现了奇数次,其他数都出现了偶数次,找出这两个数 */ public static void printOddTimesNum1(int[] arr) { // ①其中有一种数出现了奇数次,其他数都出现了偶数次,找出该数 int eor = 0; for (int cur : arr) { eor ^= cur; } System.out.println(eor); } public static void printOddTimesNum2(int[] arr) { // ②其中有两种数出现了奇数次,其他数都出现了偶数次,找出这两个数 int eor = 0; for (int cur : arr) { eor ^= cur; } // eor = a^b // eor != 0 // eor必有一个位置上是1 // eor与运算eor的补码,可以提取出最右侧的1 int rightOne = eor & (~eor + 1); int onlyOne = 0; for (int cur : arr) { if ((cur & rightOne) == 0) { onlyOne ^= cur; } } System.out.println("a=" + onlyOne + ",b=" + (onlyOne ^ eor)); } }
-
求出某个数最右侧的1
假设要求eor最右侧的1:eor与运算eor的补码eor & (~eor + 1)
求两个数的中间值
(left + right) / 2
等价于 left + ((right - left) >> 1)
-
这样写的目的一个是为了防止
(left + right)
出现溢出,一个是用右移操作替代除法提升性能。 -
left + ((right -left) >> 1)
对于目标区域长度为奇数而言,是处于正中间的,对于长度为偶数而言,是中间偏左的。因此左右边界相遇时,只会是以下两种情况:-
left/mid
,right
(left, mid 指向同一个数,right指向它的下一个数) -
left/mid/right
(left, mid, right 指向同一个数)
-
即因为mid
对于长度为偶数的区间总是偏左的,所以当区间长度小于等于2时,mid
总是和 left
在同一侧。
二、LeetCode题目
给你一个非负整数 x ,计算并返回 x 的算术平方根 。
由于返回类型是整数,结果只保留整数部分 ,小数部分将被舍去 。注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
示例 1:
输入:x = 4 输出:2
示例 2:
输入:x = 8 输出:2 解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
提示:
- 0 <= x <= 231 - 1
这道题我开始是按照二分查找的思路做的,但官方题解给出了其他的思路
袖珍计算器算法
是一种用指数函数 e x p exp exp 和对数函数 l n ln ln 代替**平方根函数**的方法
将 x \sqrt{x} x 写成幂的形式 x 1 / 2 x^{1/2} x1/2 ,再使用自然对数 e e e 进行换底,即可得到
x = x 1 / 2 = ( e l n x ) 1 / 2 = e 1 2 l n x \sqrt{x}=x^{1/2}=(e^{ln\ x})^{1/2}=e^{\frac{1}{2}ln\ x} x=x1/2=(eln x)1/2=e21ln x
由此来估计 x \sqrt{x} x 的值
class Solution {
public int mySqrt(int x) {
if (x == 0) {
return 0;
}
int ans = (int) Math.exp(0.5 * Math.log(x));
return (long) (ans + 1) * (ans + 1) <= x ? ans + 1 : ans;
}
}
打比赛可以用,面试就算了,我觉得面试官真出了这道题,应该就是想考察二分查找
class Solution {
public int mySqrt(int x) {
if (x <= 1) return x;
int left = 1, right = x-1;
while (left <= right){
int mid = left + ((right - left) >> 1);
if (mid > x/mid){
right = mid - 1;
} else if(mid < x/mid) {
if ((mid+1) > x/(mid+1))
return mid;
left = mid + 1;
} else {
return mid;
}
}
return -1;
}
}