LeetCode_位运算
位运算
- 1.总结
- 1.1.异或
- 1.2.不用额外变量交换两个整数
- 1.3.所有偶数位为和所有奇数位为1
- 1.4.针对数组中元素两两组合的写法
- 1.4.Brian Kernighan 算法
- 2.异或(力扣136)
- 3.汉明距离(力扣461)
- 4.只出现一次的数字(力扣268)
- 5.只出现一次的数字 III(力扣260)
- 6.颠倒二进制位(力扣190)
- 7.2的幂(力扣231)
- 8.4的幂(力扣342)
- 9.交替位二进制数(力扣693 )
- 10.数字的补数(力扣476)
- 11.两整数之和(力扣371)
- 12.最大单词长度乘积(力扣318)
- 13.比特位计数(力扣338)
1.总结
1.1.异或
异或运算有以下三个性质。
1.任何数和 0做异或运算,结果仍然是原来的数,即a^0=a
;
2.任何数和其自身做异或运算,结果是 0,即a^a=0
;
3.异或运算满足交换律和结合律,即a^b^a=b^a^a=b^(a^a)=b^0=b
;
1.2.不用额外变量交换两个整数
给定两个整数a和b
a = a ^ a ^ b = b;
b = b ^ b ^ a = a;
a = a ^ b;
b = a ^ b;
a = a ^ b;
1.3.所有偶数位为和所有奇数位为1
0xaaaaaaaa 和 0xAAAAAAAA 一样,表示所有偶数位为1
0x55555555表示所有奇数位为1
1.4.针对数组中元素两两组合的写法
for (int i = 0; i < len - 1; i++) {for (int j = i + 1; j < len; j++) {}}
for(int i = 0;i < len;i ++){for(int j = 0;j < i;j ++){}}
1.4.Brian Kernighan 算法
Brian Kernighan算法可以用于清除二进制数中最右侧的1。
x&(x-1)
1.x=111,x-1 = 110,x&(x-1)=110,清除了最右侧的1
2.x=110,x-1 = 101,x&(x-1)=100,清除了最右侧的1
2.异或(力扣136)
//1.异或运算public int singleNumber(int[] nums) {int len = nums.length;int res = nums[0];for(int i = 1;i < len;i ++){res ^= nums[i];}return res;}
3.汉明距离(力扣461)
//1.利用javaAPIpublic static int hammingDistance(int x, int y) {return Integer.bitCount(x ^ y);}
//2.移位计算public static int hammingDistance2(int x, int y) {int z = x ^ y;int count = 0;while (z > 0){//看z的最后一位是否为1count += z & 1;//z右移z >>= 1;}return count;}
//3.Brian Kernighan 算法(只统计1的个数,略过0)public static int hammingDistance3(int x, int y) {int z = x ^ y;int count = 0;while (z != 0){z &= z - 1;count ++;}return count;}
4.只出现一次的数字(力扣268)
//1.数学方法public static int missingNumber(int[] nums) {int sum = 0;for (int i : nums){sum += i;}int len = nums.length;return len * (1 + len) / 2 - sum;}
//2.位运算public static int missingNumber(int[] nums) {int len = nums.length;int sum = 0;for (int i = 0; i < len; i++) {sum ^= (i ^ nums[i]);}return sum ^ len;}
5.只出现一次的数字 III(力扣260)
//1.哈希集合(最先想到的方法)public static int[] singleNumber(int[] nums) {//或者是用map也可以Set<Integer> set = new HashSet<>();for (int i : nums){if (set.contains(i)){set.remove(i);}else{set.add(i);}}int[] res = new int[2];int j = 0;for (int i : set){res[j ++] = i;}return res;}
//2.位运算/** 就是通过先求二进制异或和,选择一个为1的位,那么两个数在这个位上一* 定是一个为1一个为0,通过与运算就可以把它们分组,就变成了只出现一次* 的数字Ⅰ问题,再异或一次就好* */public static int[] singleNumber2(int[] nums) {int nAdd = 0;for (int num : nums){nAdd ^= num;}int i = 1;while ((i & nAdd) == 0){i <<= 1;}int nAdd1 = 0,nAdd2 = 0;for (int num : nums){if ((i & num) != 0){nAdd1 ^= num;}else{nAdd2 ^= num;}}return new int[]{nAdd1,nAdd2};}
6.颠倒二进制位(力扣190)
//1.位运算public int reverseBits(int n) {int res = 0;for(int i = 0;i < 32;i ++){if(n == 0) break;//找到为1的位并反转int temp = ((n & 1) << (31 - i));//将所有反转之后的1组合res |= temp;//n无符号右移n >>>= 1;}return res;}
//2.调用javaAPIpublic int reverseBits2(int n) {return Integer.reverse(n);}
7.2的幂(力扣231)
//1.自己想的,统计1出现的次数public boolean isPowerOfTwo(int n) {int countOne = 0;while(n > 0){countOne += (n & 1);n >>= 1;}return countOne == 1 ? true : false;}
//2.技巧public static boolean isPowerOfTwo2(int n) {//如果n是2的幂次方,则n和n-1的每一个对应位必定是一个为0一个为1return n > 0 && (n & (n - 1)) == 0;}
//3.技巧public static boolean isPowerOfTwo3(int n) {//如果n是2的幂次方,则n和-n按位做与,最终结果定是nreturn n > 0 && (n & -n) == n;}
//4.技巧public static boolean isPowerOfTwo4(int n) {return n > 0 && Integer.bitCount(n) == 1;}
8.4的幂(力扣342)
//1.使用循环public static boolean isPowerOfFour(int n) {while ( n > 0 && Integer.bitCount(n) == 1){if ((n & 1) == 1){return true;}n >>= 2;}return false;}
//2.技巧public static boolean isPowerOfFour2(int n) {//0xaaaaaaaa 和 0xAAAAAAAA 一样,表示所有偶数位为1return n > 0 && (n & (n - 1)) == 0 && (n & 0xaaaaaaaa) == 0;
// 0x55555555表示所有奇数位为1
// return n > 0 && (n & (n - 1)) == 0 && (n & 0x55555555) != 0;}
//3.技巧public static boolean isPowerOfFour3(int n) {//是2的幂次方并且模上3为1,则为4的幂次方//假设4的幂次方为4^x,则4^x % 3 = 4^(x-1) * (4%3) = 4^(x-1),所以最终为1return n > 0 && (n & (n - 1)) == 0 && n % 3 == 1;}
9.交替位二进制数(力扣693 )
//1.自己想的public static boolean hasAlternatingBits(int n) {//看相邻位是否相同while (n > 0){if ((n & 1) == (n >> 1 & 1)){return false;}n >>= 1;}return true;}
/*2.* 分析:* 如果n是交替的01,对于它右移一位后得到的m,* 存在n跟m在二进制下必然是0和1对应的(对位)。异或运算必定都是1;** 举个栗子:5=101 5>>1=10,5^(5>>1)=111* 101* 10 =111*假设异或结果共有N位为1,则将此结果加1之后,第N+1位为1,从1到N位都为0,*此时再将两者做与运算,则最终结果必为0*/public static boolean hasAlternatingBits2(int n) {int temp=n^(n>>1);return (temp&(temp+1))==0;}
//3.和1的思路是一样的public static boolean hasAlternatingBits3(int n) {while (n > 0){if ((n % 2 == n / 2 % 2)){return false;}n /= 2;}return true;}
10.数字的补数(力扣476)
//1.自己想的public int findComplement(int num) {int res = 0;//用来记录当前到了第几位int count = 0;while(num > 0){//如果当前位为1,则取反为0,不计数if((num & 1) == 1){count ++;}else{//如果当前位为0,则取反为1,计数res += (1 << count);count ++;}num >>= 1;}return res;}
//2.求掩码/*思路:5 -> 101 , 其补码为 010 ,将 101 与 111 做 异或即可达到他对应的补码问题的关键为:求掩码*//*1 -> 111 -> 3111 -> 71111 -> 15...* */public static int findComplement2(int num) {int pre = 1,i = 1;while (pre < num){pre += Math.pow(2,i);i ++;}return pre ^ num;}
//3.思路和1一样public static int findComplement3(int num) {int temp = num;int pre = 0;while(temp > 0){temp >>= 1;pre = (pre << 1) + 1;}return num ^ pre;}
11.两整数之和(力扣371)
//1.位运算//可以将b作为进位来看待public int getSum(int a, int b) {//如果存在进位,就要加入到总和中去while(b != 0){//计算对应位能形成进位的位置int temp = (a & b) << 1;//计算对应位不能形成进位的位置a = a ^ b;b = temp;}//如果不存在进位了,返回结果return a;}
12.最大单词长度乘积(力扣318)
//1.位运算public static int maxProduct(String[] words) {int len = words.length;int[] arr = new int[len];for (int i = 0;i < len;i ++){for (char c : words[i].toCharArray()){/*arr[i]记录的是第i个字符串中26个字母是否出现abc -> 000111def -> 111000如果相与为0,则两个字符串中没有相同的字母* */arr[i] |= 1 << (c - 'a');}}int max = 0;for (int i = 0; i < len - 1; i++) {for (int j = i + 1; j < len; j++) {if ((arr[i] & arr[j]) == 0){max = Math.max(max,words[i].length() * words[j].length());}}}
// for(int i = 0;i < len;i ++){
// for(int j = 0;j < i;j ++){
// if ((arr[i] & arr[j]) == 0){
// max = Math.max(max,words[i].length() * words[j].length());
// }
// }
// }return max;}
13.比特位计数(力扣338)
//1.分别计算每个数的位为1的个数,再加和,时间复杂度为O(nlogn)public int[] countBits(int n) {int[] ans = new int[n + 1];for (int i = 0; i <= n; i++) {ans[i] = countOne(i);}return ans;}public int countOne(int n) {int res = 0;while (n > 0) {if ((n & 1) == 1) {res++;}n >>= 1;}return res;}
//2.分奇数偶数,时间复杂度为O(n)/** 偶数的二进制1的个数超级简单,因为偶数是相当于被某个更小的数乘2,* 乘2怎么来的?在二进制运算中,就是左移一位,也就是在低位多加1* 个0,那样就说明dp[i] = dp[i / 2]* 奇数稍微难想到一点,奇数由不大于该数的偶数+1得到,偶数+1在二* 进制位上会发生什么?会在低位多加1个1,那样就说明dp[i] = dp[i-1] + 1,* */public int[] countBits2(int n) {int[] dp = new int[n + 1];for (int i = 0; i <= n; i++) {if (i % 2 == 0) {dp[i] = dp[i / 2];} else {dp[i] = dp[i - 1] + 1;}}return dp;}
//3.位运算/** i&(i-1):此值必定比i(i>0)小* 1.如果i的最低位为1,i&(i-1)相当于去掉i最低位上的1,如i=11,i&(i-1) = 10* 2.如果i的最低位不为1,i&(i-1)相当于去掉从最低位向最高位遍历过程中第一个不为0的位上的1* 如i=110,i&(i-1) = 100;i=100,i&(i-1)=000;* 总的来说:就是清除掉最右侧的1** 因为i & (i - 1)的1的个数已经在前面算过了,所以i的1的个数就是 i & (i - 1)的* 1的个数加上1* */public int[] countBits3(int n) {int[] res = new int[n + 1];for (int i = 1; i <= n; i++) {res[i] = res[i & (i - 1)] + 1;}return res;}
//4.位运算/** i >> 1会把最低位去掉,因此i >> 1 也是比i小的,同样也是在前面的数组里算过。* 1.当 i 的最低位是0,则 i 中1的个数和i >> 1中1的个数相同;* 2.当i的最低位是1,i 中1的个数是 i >> 1中1的个数再加1* */public int[] countBits4(int n) {int[] dp = new int[n + 1];for (int i = 0; i <= n; i++) {dp[i] = dp[i >> 1] + (i & 1);}return dp;}