思想: 有序数组,从中找值
实现:
while 循环:时间复杂度:log(n)
public static int binarySearch01(int[] arr, int target) {
int i = 0;
int j = arr.length - 1;
// <= 是因为目标值可能在 0 或 arr.length - 1 索引处
while (i <= j) {
// 用移位运算防止溢出
int mid = (i + j) >>> 1;
if ( target < arr[mid]) {
j = mid - 1;
} else if (arr[mid] < target){
i = mid + 1;
} else {
return mid;
}
}
return -1;
}
优化:把 j 只作为一个右边界,不能指向目标值
public static int binarySearch02(int[] arr, int target) {
int i = 0;
int j = arr.length;
// 这里如果使用 i <= j,会导致查找不存在的元素的时候出现死循环
while (i < j) {
int mid = (i + j) >>> 1;
if (arr[mid] > target) {
j = mid;
} else if (arr[mid] < target){
i = mid + 1;
} else {
return mid;
}
}
return -1;
}
分析: 往左查找的消耗是往右查找消耗的 1/2,不均衡。(因为往右查找需要比较两次 if)
优化:平均两侧查找消耗
public static int binarySearch04(int[] arr, int target) {
int i = 0;
int j = arr.length;
while (1 < j - i) {
int mid = (i + j) >>> 1;
if (target < arr[mid]) {
j = mid;
} else {
// 这里不能 + 1了,如果目标值就在中间,就找不到了
i = mid;
}
}
if (arr[i] == target) {
return i;
}
return -1;
}
Arrays.binarySearch
包含多种类型的数据,这里看 int 的:
binarySearch0
的返回值 = - 插入点 - 1
为什么不直接返回 -插入点?答:为了防止插入点为0出现歧义
public static int binarySearch(int[] a, int key) {
return binarySearch0(a, 0, a.length, key);
}
private static int binarySearch0(int[] a, int fromIndex, int toIndex,
int key) {
int low = fromIndex;
int high = toIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
int midVal = a[mid];
if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found.
}
利用 api 写个二分查找,找到目标值就返回索引 + 数组,找不到就插入再返回
public static Map<Integer, int[]> binarySearch05(int[] arr, int target) {
int i = Arrays.binarySearch(arr, target);
Map<Integer, int[]> res = new HashMap<>();
if (0 < i) {
res.put(i, arr);
return res;
}
int targetIndex = abs(i + 1);
int[] arr0 = new int[arr.length + 1];
System.arraycopy(arr, 0, arr0, 0, targetIndex);
arr0[targetIndex] = target;
System.arraycopy(arr, targetIndex, arr0, targetIndex + 1, arr.length - targetIndex);
res.put(targetIndex, arr0);
return res;
}
二分查找返回最左/最右的索引(相同元素情况)
/**
* @Desc falg = -1 返回最左索引,flag = 1 返回最右索引
*/
public static int binarySearchLeftOrRight(int[] arr, int target, int flag) {
int i = 0;
int j = arr.length - 1;
int index = -1;
while (i <= j) {
int mid = (i + j) >>> 1;
if (target < arr[mid]) {
j = mid - 1;
} else if (arr[mid] < target) {
i = mid + 1;
} else {
index = mid;
if (1 == flag) {
i = mid + 1;
} else {
j = mid - 1;
}
}
}
return index;
}
优化: 二分查找返回最左/最右的索引(相同元素情况)
/**
* @param flag = -1 返回最左索引, = 1 返回最右索引
* @return int 存在,返回对应索引,不存在,返回 - 插入点 - 1
*/
public static int binarySearchLeftOrRight(int[] arr, int target, int flag) {
int i = 0;
int j = arr.length - 1;
while (i <= j) {
int mid = (i + j) >>> 1;
if (flag == 1 ? target < arr[mid] : target <= arr[mid]) {
j = mid - 1;
} else {
i = mid + 1;
}
}
int index = flag == 1 ? i - 1 : i;
if (target == arr[index]) {
return index;
}
return - index - 1;
}
思考: 有些小数部分是无法清零的该怎么处理?
这时候就要引入误差/精度了,比如说:0.33转换成二进制,误差小于1‰,那么我假设转换后的结果为 x,那么要求就是: |x - 0.33| < 1‰,即 0.319 < x < 0.331。
那么如何快速计算出结果呢?
进制转换工具
所以 x 取 0.0101010001 就OK了
验证一下:
没问题。
1*2^7 + 0*2^6 + 1*2^5 + 0*2^4 + 1*2^3 + 1*2^2 + 0*2^1 + 1*2^0
1*2^-1 + 1*2^-2 + 0*2^-3 + 1+2^-4
机器数: 数字在计算机中的二进制表示形式。在计算机中,数字以二进制形式存储和处理。机器数的最高位是符号位,用于表示数字的正负。正数的符号位为0,负数的符号位为1。机器数的大小受到计算机字长的限制,字长决定了机器数的表示范围。
// 不同位的计算机中表示 5
00000101 # 8位
00000000 00000000 00000000 00000101 # 32位
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 # 64位
64位计算机能一次读8byte,32位能一次读 4byte,可通过 cmd 输入
systeminfo
查看计算机参数
比如某机器的机器数大小是 2 byte,那么它存储数字 5 的机器数就是:0000 0101,最左边的 0 表示正数,其表示 -5 的机器数就是 1000 0101
真值: 带符号位的机器数对应的真正数值,可以通过将机器数转换为十进制数来获得。
比如:机器数 1000 0101 的真值是 -5,0000 0101 的真值是 5。
原码: 最高位表示符号位,其余位表示数值的绝对值,和机器数一个意思。
反码: 正数的反码和原码相同,负数的反码是在其原码的基础上,符号位不变,其余各位取反。
比如:正数+5的反码和原码都是00000101,负数-5的反码是11111010 。
补码: 正数的补码和原码相同,负数的补码是在其反码的基础上加1。
比如:正数+5的补码和原码都是00000101,负数-5的补码是11111011。
小结一下:对于正数:原码 = 反码 = 补码;对于负数:反码 = 原码标志位不变,其余取反(1变成0,0变成1),补码 = 反码 + 1。
补充:负数的补码转原码
加法: 正数 + 负数 我们放到减法里面说
减法:
乘法:
除法:
二进制移位运算:
<<
左移:将一个数的二进制表示向左移动指定的位数,右侧用0填充<<
看做是 *2
操作>>
右移:将一个数的二进制表示向右移动指定的位数,正数高位用0填充,负数高位用1填充>>
看做是 /2
操作,一直右移,正数会变成 0,负数会变成 -1(为了保证它是负数,所以会补个 1)>>>
无符号右移位,不管正数还是负数,高位都用0补齐注意事项1: 在做移位运算的时候要考虑当前类型的大小,如下:
声明一个 int 整型 -11、11 -11(int) >>> 2 : 原二进制:11111111111111111111111111110101 二进制: 111111111111111111111111111101 十进制: 1073741821 11(int) << 31 : 原二进制: 1011 二进制: 10000000000000000000000000000000 十进制: -2147483648 11(int) << 32 : 原二进制: 1011 二进制: 1011 十进制: 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
注意事项2: Java 中会将最高位看做符号位!,所以当数值 > 2^31 的时候,最高位会被当做符号位 1,从而编程负数。
public static void main(String[] args) { int i = (int) pow(2, 31); int j = 1; System.out.println(i); System.out.println(Integer.toBinaryString(i)); System.out.println(i + j); System.out.println(Integer.toBinaryString(i + j)); System.out.println( (i + j) / 2 ); System.out.println( (i + j) >>> 1); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10