一、前置知识点:左移、右移、无符号右移

  1. <<:左移 左边最高位丢弃,右边补0(高位向左移动,低位补零)

  • 若左移时舍弃的高位不包含1,则每左移一位,相当于该数乘以2。

  1. >>:右移 最高位是0,左边补0;最高为是1,左边补1

  • 为非负数时,>> 1和/ 2的结果是一样的

  • 为负数且还是偶数时,>> 1和/ 2的结果是一样的

  • 为负数且还是奇数时,>> 1和/ 2的结果是不一样的

  1. >>>:无符号右移 无论最高位是0还是1,左边补0

二、位图的功能和实现

引入:假设有一个集合可以帮我们收集数字,既可以add,也可以去contains。但是存在空间问题:每个整型数字(起码)占4Byte(字节)。若集合中此时只有一个整数,而一个整数有32位(4Byte*8bit),足以表示0~31之间的数字在集合中有没有。

初始是:0000...00000000 --> 一共32位

把5位置标为1即可以表示集合中有5:0000...00100000

结论:只需要4个字节即32个bit即可表示0~31之间数字是否出现过。

推广:则int[32]-> 可以表示32*32=1024个数。

功能:位图可以做出一个集合,在数字范围(最大值)确定情况下,可使用位图收集数字并判断某个数存不存在。优点就在于它可以极大压缩空间。

实现:

public static class BitMap {private long[] bits;    //一个long类型可表示64个数public BitMap(int max) {//在数字范围确定情况下传入最大值//(max + 64) >> 6  -> (max + 64) / 64   即一共需要几位存数字bits = new long[(max + 64) >> 6];}public void add(int num) {/*1.首先num/64可以定位到是哪个整数   num >> 6  ->  num/64   -->找到第几个整数2.(num & 63) -> (num % 64)  63即000..111111,&操作把前面全变成0了,剩下的等同于num%64 -->找到第几位用于描述num3.why? 因为+ - * / %比位运算>> << & | ^慢多了4. 1L<<(num & 63) -> 把1移到描述num的位置通过或运算标到位图里例如170:  170/64=2,170%64=42,  1L<<42然后和000...0000进行或运算来完成num的add操作*/bits[num >> 6] |= (1L << (num & 63));}public void delete(int num) {/*例如删170,先170/64=2再170%64=42,然后到bit[2]中描述num的位置改成0;不管描述num的位置是0还是1,和0进行&操作都会变成0->等同于删掉*/bits[num >> 6] &= ~(1L << (num & 63));}public boolean contains(int num) {//描述num的位置和1进行&操作-> 若是1: 1&1->1 !=0 返回true即存在return (bits[num >> 6] & (1L << (num & 63))) != 0;}}

三、使用位运算实现加法

两数相加思路:

int a = 46; //0101110

int b = 20; //0010100

1.异或运算也叫无进位相加

0101110 ^ 0010100 -> 0111010

2.得到a、b的进位信息:先&再左移1位

0101110 & 0010100 -> 0000100 -> 0001000

3. a+b=a^b+进位信息

0111010 + 0001000 = 1000010 -> 66

代码

public static int add(int a, int b) {int sum = a;while (b != 0) {sum = a ^ b;    //无进位相加的信息b = (a & b) << 1;   //然后计算进位信息 -> b->b'(进位信息)a = sum;    //a -> a'  无进位相加信息}return sum;}

四、使用位运算实现减法

a-b即a+b的相反数

代码

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

五、使用位运算实现乘法

a=0110

b=0111

则a*b=ans=0110+01100+011000

代码

public static int multi(int a, int b) { //支持正负int res = 0;while (b != 0) {if ((b & 1) != 0) { //等于0则不加此数res = add(res, a);  //结果接收a}a <<= 1;    //左移右边补0b >>>= 1;   //无符号右移左边补0}return res;}

六、使用位运算实现除法

小例:正数除以正数

a:01101100 b:00001100

2.小例:a/b=c 若按乘法思路: a=b*c

b=01110 c=00110

a=b*2¹+b*2² (c在第1、2位上是1) 反过来求c:

3.小例: a=01101100 b=00000011 求a/b

得到最终结果:100100(5位、2位有1)

4.小例:

减掉之后周而复始

代码

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;// x/yfor (int i = 30; i >= 0; i = minus(i, 1)) {//代码上是x去右移匹配y->因为y左移到头可能改变符号位if ((x >> i) >= y) {    //30->0位res |= (1 << i);    //移动到某个位置可以减掉时把结果设置上x = minus(x, y << i);   //此时y左移被x减掉}}//如果a、b符号不一样,取个负号返回return isNeg(a) ^ isNeg(b) ? negNum(res) : res;}

怎么解决系统最小值转绝对值

例子1:假设-10(最小)~9(最大)

问题:当使用-10/2无法转成10/2,因为系统最大没有10。

第一步: -10+1=-9

第二步:求-9/2=-4

第三步:再乘回去看差多少--> -4*2=-8 -->发现和-10相差-2

第四步:-2/2=-1

第五步:最后让-4加上-1等于-5

例子2:-15~14 问题-15/3=?

第一步:-15+1=-14

第二步:-14/3=-4

第三步:-4*3=-12 --> -15-(-12)=-3

第四步:-3/3=-1

第五步:-1+(-4)=-5

成功绕过-15无法转成绝对值的问题!

代码

public static int divide(int a, int b) {//在所有整数中只有系统最小值是没法转成绝对值的->怎么解决系统最小值转绝对值?if (a == Integer.MIN_VALUE && b == Integer.MIN_VALUE) {return 1;   //1.ab都是系统最小,除完返回1} else if (b == Integer.MIN_VALUE) {return 0;   //2.a不是最小,b系统最小,除完返回0  因为b的绝对值最大:a/b返回0} else if (a == Integer.MIN_VALUE) {    //3. a系统最小,b不是最小->再谈论if (b == negNum(1)) {//如果是系统最小除以-1则返回系统最大(力扣的约定)return Integer.MAX_VALUE;} else {//  a/b//  (a + 1) / b = c//  a - (b * c) = d//  d / b =e//  c + eint c = div(add(a, 1), b);return add(c, div(minus(a, multi(c, b)), b));}} else {    //4.  a、b都不是系统最小-->div方法直接用return div(a, b);}
}

七、完整代码

位图:

public class Code02_BitMap2 {// 这个类的实现是正确的public static class BitMap {private long[] bits;public BitMap(int max) {bits = new long[(max + 64) >> 6];}public void add(int num) {bits[num >> 6] |= (1L << (num & 63));}public void delete(int num) {bits[num >> 6] &= ~(1L << (num & 63));}public boolean contains(int num) {return (bits[num >> 6] & (1L << (num & 63))) != 0;}}public static void main(String[] args) {System.out.println("测试开始!");int max = 10000;BitMap bitMap = new BitMap(max);HashSet<Integer> set = new HashSet<>();int testTime = 10000000;for (int i = 0; i < testTime; i++) {int num = (int) (Math.random() * (max + 1));double decide = Math.random();if (decide < 0.333) {bitMap.add(num);set.add(num);} else if (decide < 0.666) {bitMap.delete(num);set.remove(num);} else {if (bitMap.contains(num) != set.contains(num)) {System.out.println("Oops!");break;}}}for (int num = 0; num <= max; num++) {if (bitMap.contains(num) != set.contains(num)) {System.out.println("Oops!");}}System.out.println("测试结束!");}}

加减乘除

// 测试链接:https://leetcode.com/problems/divide-two-integers
public class Code03_BitAddMinusMultiDiv {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 {int c = div(add(a, 1), b);return add(c, div(minus(a, multi(c, b)), b));}} else {return div(a, b);}}}

zcy算法入门笔记002-位图相关推荐

  1. zcy算法入门笔记004

    路径总和 判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum .如果存在,返回 true :否则,返回 false . public static c ...

  2. zcy算法入门笔记001

    笔记仅用于学习,如有错误欢迎指正,谢谢 位运算 public class Code01_PrintBinary {public static void print(int num){//十进制=> ...

  3. zcy算法入门笔记003-树

    相同的树 public static class TreeNode {public int val;public TreeNode left;public TreeNode right;}public ...

  4. 刘汝佳算法入门笔记(1)

    刘汝佳算法入门笔记 习题4-2 习题4-2 有n行n列(2≤n≤9)的小黑点,还有m条线段连接其中的一些黑点.统计这些线段连成 了多少个正方形(每种边长分别统计). 行从上到下编号为1-n,列从左到右 ...

  5. [算法入门笔记] 9. 哈希表与哈希函数

    文章目录 1. 哈希表与哈希函数的实现 2. 设计RandomPool结构 3. bitmap 3.1 概述 3.2 常用操作 3.2.1 存储数据 3.2.2 添加操作 3.2.3 删除操作 3.2 ...

  6. [算法入门笔记] 18. 动态规划

    动态规划往往是有套路的,但套路是建立在熟练的基础上的~ 文章目录 0 建议 1 机器人达到指定位置的方法数 1.1 暴力递归 1.2 记忆化搜索 1.3 动态规划 2 换钱的最少货币数 2.1 暴力递 ...

  7. 数据结构与算法入门(follow 左神)

    文章目录 一. 认识时间复杂度和简单排序算法 1.以选择排序为例 2.异或运算 3.插入排序 4.二分查找 5.对数器 二. 认识O(NlogN)的排序 1.递归==栈的后序遍历 2.归并排序 3.快 ...

  8. 推荐算法炼丹笔记:CTR点击率预估系列入门手册

    ​CTR点击率预估系列家谱 炼丹之前,先放一张CTR预估系列的家谱,让脉络更加清晰. (一)FiBiNET:结合特征重要性和双线性特征交互进行CTR预估 1.1 背景 本文发表在RecSys 2019 ...

  9. 深度学习入门笔记(六):误差反向传播算法

    专栏--深度学习入门笔记 推荐文章 深度学习入门笔记(一):机器学习基础 深度学习入门笔记(二):神经网络基础 深度学习入门笔记(三):感知机 深度学习入门笔记(四):神经网络 深度学习入门笔记(五) ...

最新文章

  1. Linux查看内存使用情况
  2. 【bzoj1597】 土地购买
  3. 神经网络与推荐系统初步简介
  4. eBay邓明:dubbo-go 中 metrics 的设计
  5. Hudson安装配置文档
  6. python3tkinter_python3使用tkinter制作动画
  7. 【多任务】多任务学习在推荐算法中的应用
  8. STM32的AD通道干扰问题
  9. windows服务初识
  10. 此计算机屏保怎么取消,如何取消屏幕保护
  11. 一款好用、易扩展的文件解析引擎,是怎么演变而来的
  12. 使用R语言进行单(双)因素方差分析
  13. 从多个文档在Word 2010中创建主文档
  14. 最贵新股没破发,此前弃购7.8个亿,背靠华为的这家半导体公司这么香?
  15. 【JavaEE】TCP的五层协议栈之应用层与传输层的UDP协议
  16. Unraid6.9.1开心版安装
  17. 推荐5款轻量级的小软件,界面简洁无广告
  18. 企业公众号怎么做内容?这四个阶段要做好
  19. 表复制:SELECT INTO 和 INSERT INTO SELECT
  20. 八数码问题中的逆序数

热门文章

  1. Django简单全文搜索(Django实现搜索功能)
  2. HTML+CSS网页设计与布局学习-------网页中常见的图像格式
  3. 同样学习Linux, 为何差别这么大? - 论打通Linux进程和内存管理任督二脉
  4. 张量网络算法基础(三、格点模型)
  5. 自然科学常识计算机,自然科学小常识
  6. Android方向传感器学习之指南针示例
  7. About Significance Tests
  8. 5分钟了解微软、用友、阿里云、谷歌等本周新动态!
  9. nbsp; quot; amp;lt; gt; 等html字符转义
  10. 河源食品安全检测实验室建设细节剖析