X Tutup
# 二进制 ## 常见二进制操作 ### 基本操作 a=0^a=a^0 0=a^a 由上面两个推导出:a=a^b^b ### 交换两个数 a=a^b b=a^b a=a^b ### 移除最后一个 1 a=n&(n-1) ### 获取最后一个 1 diff=(n&(n-1))^n ## 常见题目 [single-number](https://leetcode-cn.com/problems/single-number/) > 给定一个**非空**整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 ```c++ int singleNumber(vector& nums) { // 10 ^10 == 00 // 两个数异或就变成0 auto result = 0; for (const auto &num : nums) { result ^= num; } return result; } ``` [single-number-ii](https://leetcode-cn.com/problems/single-number-ii/) > 给定一个**非空**整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。 ```c++ // 思路:每一位,统计有多少个1,除以3取余数 // 出现三次的,取余数为0,被过滤掉,剩下的都是只出现一次的那个数 int singleNumber(vector& nums) { auto ret = 0; for (int i = 0; i < 32; ++i) { auto sumOne = 0; for (const auto &item : nums) { sumOne += (item >> i) & 1; } ret ^= (sumOne % 3) << i; } return ret; } ``` [single-number-iii](https://leetcode-cn.com/problems/single-number-iii/) > 给定一个整数数组  `nums`,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。 ```c++ vector singleNumber(vector& nums) { auto diff = 0; // 按位与,出现两次的数会被剔除掉 for (const auto &item : nums) { diff ^= item; } vector result(2); // 只出现一次的两个数不想等,则其异或的结果必然至少有一位为 1 // 找到最后一个 '1' diff &= (-diff); // 再按该位上的值为1还是为0将所有的数分成两组进行异或把两个数分开 for (const auto &num : nums) { if ((diff & num) == 0) { result[0] ^= num; } else { result[1] ^= num; } } return result; } ``` > 关于 x & (-x) > 在计算机系统中,数值一律用补码来表示和存储 > 一个正数 x 与其相反数 -x,只有一位同时为 '1',并且总为 x 中的最后一位 '1' > 正数的补码与原码相同 > 负数的补码,将其原码除符号位外的所有位取反后加1 > 10和-10用8位表示为: > 10: 0000 1010 > -10: 1111 1010 > 因此,x & (-x) 可用于求出x中最后一个'1' [number-of-1-bits](https://leetcode-cn.com/problems/number-of-1-bits/) > 编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’  的个数(也被称为[汉明重量](https://baike.baidu.com/item/%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F))。 ```c++ int hammingWeight(int n) { auto ret = 0; while (n != 0) { n = n & (n - 1); ++ret; } return ret; } ``` [counting-bits](https://leetcode-cn.com/problems/counting-bits/) > 给定一个非负整数  **num**。对于  0 ≤ i ≤ num  范围中的每个数字  i ,计算其二进制数中的 1 的数目并将它们作为数组返回。 ```c++ vector countBits(int num) { vector ret(num+1); for (int i = 0; i <= num; ++i) { ret[i] = hammingWeight(i); } return ret; } int hammingWeight(int n) { auto ret = 0; while (n != 0) { n = n & (n - 1); ++ret; } return ret; } ``` 另外一种动态规划解法 ```c++ vector countBits(int num) { vector ret(num + 1); for (int i = 1; i <= num; ++i) { // 对于 i,其二进制1的个数 = 移除其最后一个1剩余的1的个数 + 1 // i & (i - 1)移除最后一个1 ret[i] = ret[i & (i - 1)] + 1; } return ret; } ``` [reverse-bits](https://leetcode-cn.com/problems/reverse-bits/) > 颠倒给定的 32 位无符号整数的二进制位。 思路:依次颠倒即可 ```c++ uint32_t reverseBits(uint32_t n) { uint32_t ret = 0; // 记得初始化,否则通不过 auto pow = 31; while(n != 0) { // 取最后一位进行左移 ret += (n & 1) << pow; n >>= 1; --pow; } return ret; } ``` [bitwise-and-of-numbers-range](https://leetcode-cn.com/problems/bitwise-and-of-numbers-range/) > 给定范围 [m, n],其中 0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含 m, n 两端点)。 ```c++ /* * 思路: * 只要有一位不同时位1,则按位与结果为0 * 因此,从m和n最左边一个不同时为1的位起,其右边的位都将被剔除,如: * m = 5 0101 * n = 15 1111 * 后面2位最终会变为0 * 所以,掐头去尾取中间(左边起第二个1)么? * 不,n再大也没用,m跟n最左边的1必须在同一位,否则结果直接为0 * 毕竟终归是会有一个中间的数,10000,按位与完,m就没了 * * 所以,m、n同时右移,直到二者相等(剩下最左边的若干个1,或者为0) * 再进行左移 */ int rangeBitwiseAnd(int m, int n) { auto zeroNum = 0; while (m != n) { m >>= 1; n >>= 1; ++zeroNum; } return m << zeroNum; } ``` ## 练习 - [ ] [single-number](https://leetcode-cn.com/problems/single-number/) - [ ] [single-number-ii](https://leetcode-cn.com/problems/single-number-ii/) - [ ] [single-number-iii](https://leetcode-cn.com/problems/single-number-iii/) - [ ] [number-of-1-bits](https://leetcode-cn.com/problems/number-of-1-bits/) - [ ] [counting-bits](https://leetcode-cn.com/problems/counting-bits/) - [ ] [reverse-bits](https://leetcode-cn.com/problems/reverse-bits/)
X Tutup