位运算-实现位图和计算器

常用的位运算

位运算符说明计算案例
&都是1为1, 否则即为 04 & 3 = 0100 & 0011 = 0000 == 0
|有一个为1就是14 & 3 = 0100 | 0011 = 0111 == 7
^异或不同为1 相同为07 & 3 = 0111 ^ 0011 = 0100 == 4
~取反二进制的0变成1,1变成0~4 = 11111111111111111111111111111011 = -5
<<左移左移后右边位补 01<<3 = 8
>>带符号右移右移后左边位补符号位-8>>2 = -2
>>>不带符号右移右移后左边位补 0-8>>>2 = 2

原码、反码、补码是什么

正数的原码、反码、补码都是二进制本身;
负数反码就是原码除了符号位不动,其他所有位按位取反
负数的补码是反码加一得到的(运算时包括符号位)

数值原码反码补码
+80000 10000000 10000000 1000
-81000 10001111 01111111 1000

为什么负数的二进制设计为正数取反加1

在计算机中,数值都是用补码来计算和存储的。使用补码的原因是:

  1. 使用补码可以将符号位和数值域统一处理
  2. 在硬件电路的实现上,只要使用一种加法电路就可以处理各种有符号数的加减计算,使得电路设计简单

为什么1字节的整数范围是-128到127,而不是-128到128

因为最高位代表符号位:1表示负数, 对半分就是 2^7 = 128 负数范围 -1 到 -128;正数范围 0 到 127。

位运算小技巧

取相反数

num = -num;
num = ~num + 1

取模运算

num & 63 等同于 num % 64
因为取模64就等同于舍弃高位数据 只保留低6位的数据
63的二进制位就是 0000 0000 0011 1111,和num做与运算就是舍弃掉高位信息

交换两个数

原理: a ^ 0 = a ; a ^ a = 0

private static void swap (int[] arr, int i, int j) {
        arr[i]  = arr[i] ^ arr[j];
        arr[j]  = arr[i] ^ arr[j]; // arr[i] ^ arr[j] ^ arr[j] = arr[i] 
        arr[i]  = arr[i] ^ arr[j]; // arr[i] ^ arr[j] ^ arr[i] = arr[j] 
    }

判断奇偶数

原理:num & 1 如果最后一位是1则代表是奇数 结果不等于0。否则为偶数

System.out.println( (3 & 1) == 0);

打印整数的二进制信息

原理:int类型占用4字节一共32bit, 打印31位到0位的bit位,1左移之后 其他的bit位都是0,如果31位与num等于0 则31位肯定是0

private static void print(int num) {
        int c = 0;
        for (int i = 31; i >= 0; i--) {
            c++;
            System.out.print( (num & (1 << i)) == 0 ? "0" : "1");
            //按4分隔加空格
            if((c & 3) == 0) {
                System.out.print(" ");
            }
        }
    }

只出现奇数次的一个数字

问题描述

给定一个非空整数数组,除了某个元素出现奇数次以外,其余每个元素均出现偶数次。找出那个只出现了奇数次的元素。
说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

public int onceNum(int[] arr) {
        int eor = 0;
        for (int i = 0; i < arr.length; i++) {
            eor ^= arr[i];
        }
        return eor;
    }

只出现奇数次的两个数字

问题描述

给定一个非空整数数组,除了两个不同元素出现奇数次以外,其余每个元素均出现偶数次。找出出现奇数次的两个元素。

思路:

  1. 先将数组中的每个数进行异或,最后的结果肯定是a^b
  2. a不等于b 则 a ^ b的结果必定有二进制位等于1的数字(这个1肯定是a 和 b的二进制位不同异或成1的)
  3. 将数组分为两部分,a 、b的数字必然在二进制位为1、0的位置
  4. 将二进制位等于1的部分进行异或 剩下的一个数必然是a或者b (假设是a)
  5. 将找到的数 a ^ a ^ b = b 得到另外的 b
public int[] twoNum(int[] arr){
        int eor = 0;
        for (int i = 0; i < arr.length; i++) {
            eor ^= arr[i];
        }
        // eor = a ^ b 不为0 必然有二进制位为1
        // 提取出最右的1
        int rightOne = eor & (~eor + 1);
        int onlyOne = 0;
        for (int i = 0 ; i < arr.length;i++) {
            //  arr[1] =  111100011110000
            // rightOne=  000000000010000
            if ((arr[i] & rightOne) != 0) {
                onlyOne ^= arr[i];
            }
        }
//        System.out.println(onlyOne + " " + (eor ^ onlyOne));
        return new int[]{onlyOne, eor ^ onlyOne};
    }

位图

public static class BitMap {
        private long[] bits;

        /**
         * bit长度62 则需要一个long字段存储 (62 + 64) / 64
         * @param max 位图大小
         */
        public BitMap(int max){
            this.bits = new long[ (max + 64) >> 6];
        }


        /**
         * num & 63 等同于 num % 64
         *      因为取模64就等同于舍弃高位数据 只保留低6位的数据
         *      63的二进制位就是 0000 0000 0011 1111
         *
         * 添加150 则属于数组中 150/64索引处,bits[2] = bits[2] |  1L << (150 % 64 = 22)
         *      bits[2] 和 1左移后的数据做或运算
         *      则将bits[2]中的long数据22位的bit位设置为1
         * @param num 添加数据到位图中
         */
        public void add(int num){
            //注意 必须写1L 不能写1, 否则1当做int无法左移超过31位
            bits[num >> 6] |= (1L << (num & 63));
        }

        /**
         * 删除某个数 只要保证bits[num/64] & 1111 1111 0111 1111 就能删除掉对应位数据
         *      先做出来 0000 0000 1000 0000 然后取反就可以得到上面的数
         * @param num 删除位图中数据
         */
        public void delete(int num) {
            bits[num >> 6] &= ~(1L << (num & 63));
        }

        /**
         * 与运算 如果不等于0 说明数据存在
         * @param num
         * @return 数据是否存在位图中
         */
        public boolean contains(int num) {
            return (bits[num >> 6] & (1L << (num & 63))) != 0;
        }
    }

统计某个数二进制位=1的个数

public static int bitCount(int num){
        int count = 0;
        while(num != 0) {
            //num取反 + 1 与 num就能得到二进制最右边为1的数字
            int right = num & ((~num) + 1);
            count++;
            num ^= right;
        }
        return count;
    }

位运算实现加减乘除

异或运算就等于无进位相加

加法

public static int add(int a, int b) {
		int sum = a;
		while (b != 0) {
			//计算a 异或 b 无进位相加
			sum = a ^ b;
			// a & b 向左移动一位得到进位信息
			b = (a & b) << 1;
			// b不等0说明还需要重新相加
			a = sum;
		}
		return sum;
	}

减法

/**
	 * @return 返回相反数
	 */
	public static int negNum(int n) {
		return add(~n, 1);
	}

	/**
	 * a - b 等同于 a + (~b + 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;
	}

	private 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);
		}
	}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值