获取一个数的二进制数标识
public void print(int num) {
for (int i = 31; i >= 0; i--) {
System.out.println((num & (1 << i)) == 0 ? "0" :"1");
}
System.out.println();
}
32位二进制无符号数的范围 0 ~ 232
32位二进制有符号数的范围 -231 ~ 231-1 解释为什么是这个范围,因为正数包含了0,所以最终到不了231这个数,否则会超出;而负数不包含0,可以到达231
对于32位有符号数,第32位表示符号,
正数的二进制表示为:0…………后面跟其所代表的的数的二进制表示;
负数的二进制表示为:1…………后面的二进制数取反+1来表示 所以一个数的负数其实可以取反+1来表示 int c = 9; d = (~c + 1) 即d = -9
举例:
-5的二进制表示为11111111111111111111111111111011
他所代表的含义为 最前面的1代表符号为负,后面31位取反+1
得到 1000000……101
也就是-5
原因:
为什么负数要取反+1:所有算数+ - * / %等都需要使用二进制来进行;方便底层计算的时候,对于这些运算符都有一套定制的计算方法,而不用判断操作数的正负性,提高系统性能
前缀和
计算一个数组中的 L 到 R 范围的和,可以使用前缀和
前缀和就是在一个数组中,按照下标位置,记录0~N的范围和;
当想要得到L到R的范围和时,就是用 0 ~ R的和减去0 ~ L-1 的和(如果L为0,则直接返回0~R的和即可)
@Test
// 前缀和
public int prefixSum(int L, int R) {
int[] nums = new int[] {9,2,5,4,1,5,7,12,3};
int N = nums.length;
// 用来记录前缀和
int[] preSum = new int[N];
preSum[0] = nums[0];
for (int i = 1; i < N; i++) {
preSum[i] = preSum[i - 1] + nums[i];
}
// 求 L 到 R 的和
return L == 0 ? preSum[R] : preSum[R] - preSum[L - 1];
}
修改随机的概率为原来的平方
任意的x,x在 [0 ~ 1)之间,[0~x)范围上的数出现的概率由原来的x调整为x的平方
@Test
// 任意的x,x在 [0 ~ 1)之间,[0~x)范围上的数出现的概率由原来的x调整为x的平方
// 例:在[0 ~ 1),x < 0.3 的概率为 0.3; 现在要改为概率为:0.3 * 0.3
public void XToXPow2() {
int testTimes = 10000000;
double x = 0.3;
int count = 0;
for (int i = 0; i < testTimes; i++) {
// 只有两次随机结果都是小于x的,才能count++,也就是变为两次结果概率乘积
double XToXPow2 = Math.max(Math.random(), Math.random());
if (XToXPow2 < x) {
count++;
}
}
System.out.println((double) count / (double) testTimes);
System.out.println((double) Math.pow((double) x, 2));
}
给定一个区间等概率获取器,获得另一个区间的等概率获取器
给定任意一个函数,规定在某个区间上的任意一个数都是等概率返回的(例如:[a, b]上的数都是等概率返回的),然后让你求另一个区间上的数,也是等概率返回的(例如:[c, d]上的数也是等概率返回的)。
可以设计算法:
- 首先根据给定区间[a, b],然后定义一个0,1随机生成器:a~b平分,[a,a1]、x、[a2,19],然后如果随机到x,则重做,直到得到其他两个区间之一,三目判断返回0,1;
- 判断,几个二进制数可以表示 d - c 的所有数,然后执行几次上述的0,1获得器,位移相加;如果得到了 [0,d-c] 上的数,就返回 + d,否则重做,就可以得到 [c,d] 上的数等概率返回了
例子:
// 题目要求, 给定 1 ~ 5 等概率返回的函数,不可再使用java的随机函数,设计一个 1 ~ 7 等概率返回的函数
// 1 ~ 5等概率返回
public int getNum15() {
return (int) (Math.random() * 5) + 1;
}
// 设计一个 返回 0 1 等概率的函数
public int getNum01() {
int ans = 0;
do {
ans = getNum15();
} while (ans == 3);
return ans < 3 ? 0 : 1;
}
// 给定一个 1 ~ 5 的等概率随机函数,设计一个 0 ~ 7 的等概率返回函数
public int getNum07() {
return (getNum01() << 2) + (getNum01() << 1) + (getNum01() << 0);
}
// 0 ~ 6等概率返回一个
public int getNum06() {
int ans = 0;
do {
ans = getNum07();
} while (ans == 7);
return ans;
}
// 最终的到 1 ~ 7 等概率返回的函数
public int getNum17() {
return getNum06() + 1;
}
变种:给定一个0,1不等概率获取器,定义一个0,1等概率获取
因为 getNum01EqualP() 会被执行两次,获得 1 1,0 0 的概率是不等的,而获得 1 0,0 1 的概率都是p(1 - p),所以只有两次都是 p(1 - p),才是等概率的,才会返回
// 0 1 不等概率获取器 例子
public int getNum01NotEqualP() {
return Math.random() > 0.8 ? 1 : 0;
}
// 改为的 0 ,1 等概率获取
public int getNum01EqualP() {
int ans = 0;
do {
ans = getNum01NotEqualP();
} while (ans == getNum01NotEqualP());
return ans;
}
二分法
// 普通二分查找
public static boolean binarySearch(int[] nums, int target) {
if (nums == null || nums.length == 0) return false;
// 确保有序
Arrays.sort(nums);
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return true;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
}
}
return false;
}
// 二分查找 找到最左位置
public static int binarySearchLeft(int[] nums, int target) {
if (nums == null || nums.length == 0) return -1;
// 确保有序
Arrays.sort(nums);
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1;
}
}
if (left >= nums.length || nums[left] != target) return -1;
return left;
}
// 二分查找 找到最右位置
public static int binarySearchRight(int[] nums, int target) {
if (nums == null || nums.length == 0) return -1;
// 确保有序
Arrays.sort(nums);
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
}
}
if (right < 0 || nums[right] != target) return -1;
return right;
}
// 局部最小值, 任意一个谷底值位置
public static int localMin(int[] nums) {
if (nums == null || nums.length == 0) return -1;
int N = nums.length;
if (nums.length == 1) return 0;
if (nums[0] < nums[1]) return 0;
if (nums[N - 1] < nums[N - 2]) return N - 1;
int left = 0;
int right = N - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid + 1] > nums[mid]) right = mid;
else left = mid + 1;
}
return right;
}
位图的实现
使用比哈希表节省极大空间的情况下,存储数字,判断是否存在
public class BitMap {
// 可以存储的最大数
private int max;
private long[] bits;
public BitMap(int max) {
// +64 保证0~63存在0位置
// 根据最大数,定义数组的最大值
this.bits = new long[(max + 64) >> 6];
}
// 存入数字
public void add(int num) {
// 让bits的第 num >> 6索引上的 第num & 63位,置为1,代表nums存在(或1为1)
// 1l << (num & 63) 代表第num & 63位上为1,其余位置全0的64位二进制表示
bits[num >> 6] |= (1l << (num & 63));
}
// 删除数组
public void delete(int num) {
// bits的第 num >> 6索引上的 第num & 63位,置为0,代表nums不存在(与0为0)
// ~(1l << (num & 63)) 代表第num & 63位上为0,其余位置全1的64位二进制表示
bits[num >> 6] &= ~(1l << (num & 63));
}
// 判断数组是否存在
public boolean contains(int num) {
// bits的第 num >> 6索引上的 第num & 63位,和1与,如果为1,返回1,说明存在;如果为0,返回0,说明不存在
// 所以结果不为0,代表存在;否则不存在
return (bits[num >> 6] & (1l << (num & 63))) != 0;
}
}
位运算实现+、-、*、/
加法:对应LeetCode 剑指 Offer 65. 不用加减乘除做加法
首先要明确:
- ^:表示二进制的无进位相加,0 1 1 0 1 + 1 0 1 1 1 = 1 1 0 1 0 (1 和 1相加进位消除为0, 1 和 0相加无进位为1)
- &:两二进制位相与,同1为1,有0则0
所以上面两个符号刚好可以配合,^进行无进位相加,然后&后向左移动一位就是进位信息(同1则需进位,向前一位进位,所以向左移动一位)
循环上述过程,无符号相加,然后再加上进位;因为加上进位之后还有可能需要进位,所以递归调用;
class Solution {
public int add(int a, int b) {
if(b == 0) {
return a;
}
if(a == 0) {
return b;
}
// 两数无符号相加
int sum = a ^ b;
// 保存进位
int carry = (a & b) << 1;
return add(sum, carry);
}
}
减法:就等于 a加上b的相反数
因为不能出现+、-、*、/,有因为b的相反数等于 ~b +1(b取反+1);所以可以用**add(a,add(~b,1))**表示
乘法
public static int mul(int a, int b) {
int res = 0; // 存储结果
// 其中一个乘数不为0,就循环相乘
while (b != 0) {
// b 的二进制末尾为1,将 a 加入结果一份
if ((b & 1) == 1) {
res = add(res, a);
}
// a 左移一位后面补0
a <<= 1;
// b 右移一位,用0补
b >>>= 1;
}
return res;
}
除法:对应LeetCode 29. 两数相除
把除数不停的往左移,当除数离被除数最近的时候就用被除数减去除数(除数中一定包含了这个左移后的数,减去接着循环);然后将结果的移位位置上设置为1
private static boolean isNeg(int num) {
return num < 0;
}
// 除法
public static int div(int a, int b) {
// 保证除数和被除数都是正数
int x = isNeg(a) ? add(~a, 1) : a;
int y = isNeg(b) ? add(~b, 1) : b;
int res = 0;
for (int i = 30; i >= 0; i = sub(i, 1)) {
// 让被除数 x 右移, 找与 y 最接近的最大2的幂
// 如果找到比 y 大的最接近的2的幂,记录i,将结果的第i为置为1,然后x减去左移i位后的y
// 周而复始,知道计算结果所有位的情况
if ((x >> i) >= y) {
res |= (1 << i);
x = sub(x, y << i);
}
}
if (isNeg(a) && isNeg(b) || !isNeg(a) && !isNeg(b)) {
return res;
} else {
return add(~res, 1);
}
}
题目解法
class Solution {
public static int add(int a, int b) {
int sum = a;
while (b != 0) {
sum = a ^ b;
b = (a & b) << 1;
a = sum;
}
return sum;
}
public static int negNum(int n) {
return add(~n, 1);
}
public static int minus(int a, int b) {
return add(a, negNum(b));
}
public static 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;
}
public static boolean isNeg(int n) {
return n < 0;
}
public static 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 = 30; i >= 0; i = minus(i, 1)) {
if ((x >> i) >= y) {
res |= (1 << i);
x = minus(x, y << i);
}
}
return isNeg(a) ^ isNeg(b) ? negNum(res) : res;
}
// 左神新手班 第五节
public static int divide(int a, int b) {
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) {
if (b == negNum(1)) {
return Integer.MAX_VALUE;
} else {
// a / b
// (a + 1) / b = c
// a - (b * c) = d
// d / b = e
// return c + e
int c = div(add(a, 1), b);
return add(c, div(minus(a, multi(c, b)), b));
}
} else {
return div(a, b);
}
}
}
比较器
比较器规则
相关题目1, LeetCode 23. 合并K个升序链表
相关题目2, LeetCode 937. 重新排列日志文件