目录
- 技巧
- 练习位运算
- [461. 汉明距离](https://leetcode-cn.com/problems/hamming-distance/)
- [190. 颠倒二进制位](https://leetcode-cn.com/problems/reverse-bits/)
- [136. 只出现一次的数字](https://leetcode-cn.com/problems/single-number/)
- [260. 只出现一次的数字 III](https://leetcode-cn.com/problems/single-number-iii/)
- [268. 丢失的数字](https://leetcode-cn.com/problems/missing-number/)
- [693. 交替位二进制数](https://leetcode-cn.com/problems/binary-number-with-alternating-bits/)
- [476. 数字的补数](https://leetcode-cn.com/problems/number-complement/)
- 练习二进制思想
- [342. 4的幂](https://leetcode-cn.com/problems/power-of-four/)
- [318. 最大单词长度乘积](https://leetcode-cn.com/problems/maximum-product-of-word-lengths/)
- [338. 比特位计数](https://leetcode-cn.com/problems/counting-bits/)
- 一些补充
- 剑指 Offer 56 - I. 数组中数字出现的次数
技巧
位运算的一些基础技巧:
x ^ 0... = xx ^ 1... = ~xx ^ x = 0x & 0... = 0x & 1... = xx & x = xx | 0... = xx | 1... = 1...x | x = x
进阶技巧:
n & (n-1) 可以去除n的位级表示中最低的一位:
如 n = 11110100 , n - 1 = 11110011
n & (n-1) = 11110000
n & (-n) 可以得到n的位级表示中最低的那一位:
如 n = 11110100 , 取负:-n = 00001100
n & (-n) = 00000100
练习位运算
461. 汉明距离
先异或运算,然后统计1个数:
class Solution {
public:int hammingDistance(int x, int y) {int XOR_result = x ^ y;int result = 0;while(XOR_result != 0){result += (XOR_result & 1);XOR_result = XOR_result >> 1;}return result;}
};
190. 颠倒二进制位
result不断左移,最低位加上n的最低位,n不断右移。
class Solution {
public:uint32_t reverseBits(uint32_t n) {uint32_t result = 0;for(int i = 0; i < 32; i++){result = result << 1;result += n & 1;n = n >> 1;}return result;}
};
136. 只出现一次的数字
思路一:哈希先扫一遍,然后再扫一遍,找到second为1的it。
class Solution {
public:int singleNumber(vector<int>& nums) {unordered_map<int,int> umap;int result = 0;for(int i = 0; i < nums.size(); i++){umap[nums[i]]++;}for(auto it : umap){if(it.second == 1) return it.first;}return 0;}
};
思路二:利用 x ∧ x = 0 和 x ∧ 0 = x 的特点,将数组内所有的数字进行按位异或。出现两次
的所有数字按位异或的结果是 0,0 与出现一次的数字异或可以得到这个数字本身。神仙思路!
class Solution {
public:int singleNumber(vector<int>& nums) {int result = 0;for(int num : nums)result = num ^ result;return result;}
};
260. 只出现一次的数字 III
这次是有两个不同的元素,那么用位运算如何做呢?
觉得这个题解写的很好,浅显易懂。
某题解
编程时注意一下细节:
1、&的优先级比==低,所以需要加括号
2、用int作为res会报错,需要用long
class Solution {
public:vector<int> singleNumber(vector<int>& nums) {long res = 0;for(int i = 0; i < nums.size(); i++){res = res ^ nums[i];}//此时res = a ^ b;//找出1的位级表示中最低的那一位,代表着a与b在这一位是不一样的,假设a在这一位为0,b在这一位为0res = res & (-res);int a = 0;int b = 0;for(int i = 0; i < nums.size(); i++){if((nums[i] & res) == 0) //不可能b,而且除了a其他符合要求的都是重复的a = nums[i] ^ a;else b = nums[i] ^ b;}return {a,b};}
};
268. 丢失的数字
XOR原理:x ^ x = 0; 0 ^ y =y;
两个相同的数字XOR之后为0,如果这个数字只出现了一次,那么XOR之后还是本身。这一题本质上和上一题是一样的。
一些细节,看注释
class Solution {
public:int missingNumber(vector<int>& nums) {int n = nums.size();int result = n; //因为for循环中i不能为n,而我们必须遍历[0,n],所以初值设置为nfor(int i = 0; i < n; i++) //遍历nums数组,其丢失数在nums[] + [0..n]中只出现了一次{result = result ^ i ^ nums[i];}return result;}
};
693. 交替位二进制数
class Solution {
public:bool hasAlternatingBits(int n) {int prev_tail = n & 1;while(n){int now_tail = (n >> 1) & 1;if(now_tail == prev_tail) return false;prev_tail = now_tail;n = n >> 1;}return true;}
};
476. 数字的补数
思路一:一个一个来,注意倒序。
class Solution {
public:int findComplement(int n) {int result = 0;vector<int> res;while(n){res.emplace_back(!(n & 1));n = n >> 1;}for(int i = res.size() - 1; i >=0; i--){result = (result << 1) | res[i];}return result;}
};
当然也可以使用栈来做,思路一样的:
class Solution {
public:int findComplement(int n) {int result = 0;stack<int> res;while(n){res.push(!(n&1));n >>= 1;}while(!res.empty()){result = (result << 1) | res.top();res.pop();}return result;}
};
思路二:使用异或
举例:
5的二进制是:0101,7的二进制是: 0111,它们的抑或为:0010,去掉前导零位即为取反。
再来一个例子,假设a为1110 0101,b为1111 1111,a^b = 0001 1010是a的取反。也就是说二进制位数与num相同,且全为1的数tmp与num的抑或即为所求。
class Solution {
public:int findComplement(int n) {int tmp=0;int tmp_n = n;while(n){tmp = (tmp << 1) | 1;n >>= 1;}return tmp ^ tmp_n;}
};
练习二进制思想
342. 4的幂
先考虑2的次方:
如果n为2的整数次方,那么它的二进制一定是0...1...0
的形式;那么n-1的二进制应该是0..011...1
的形势。这两个数按位求与的结果一定为0。
如果n为4的整数次方,它一定为2的偶数幂。所以4的幂与二进制数(1010101010…10)相与会得到0.
int 为32位,每4位1010对应16进制a,所以应该为0xaaaaaaaa;
注意&与==的优先级。
class Solution {
public:bool isPowerOfFour(int n) {return ( n > 0 && (n & n-1) == 0 && (n & 0xaaaaaaaa) == 0);}
};
318. 最大单词长度乘积
怎样快速判断两个字母串是否含有重复数字呢?可以为每个字母串建立一个长度为26的二进制数字,每个位置表示是否存在该字母。如果两个字母串含有重复数字,那它们的二进制表示的按位与不为0。同时,我们可以建立一个哈希表来存储字母串(在数组的位置)到二进制数字的映射关系,方便查找调用。
class Solution {
public:int maxProduct(vector<string>& words) {unordered_map<int,int> hashmap; //使用哈希表来存储(位掩码 -> 单词长度)int ans = 0;for(auto word : words) //遍历每个string{int mask = 0; //位掩码int size = word.size(); //单词长度for(auto c : word) //encode位掩码mask = mask | (1 << (c-'a'));hashmap[mask] = max(hashmap[mask],size);//如果掩码相同,存储更长的字符串(说明有些字符重复)//对比当前单词与之前的所有单词,无重复字符,且长度乘积大于ans则更新ansfor(auto it : hashmap){if((mask & it.first)== 0) //如果无重复字符{ans = max(ans,size*it.second);}}}return ans;}
};
338. 比特位计数
利用dp+位运算。
定义一个数组dp,dp[i]表示数字i的二进制含有1的个数。
对于第i个数字,如果它二进制的最后一位为1,那么它含有1的个数则为dp[i] = dp[i-1]+1;
如果它二进制的最后一位为0,那么它含有1的个数和其右移结果相同,即dp[i] = dp[i>>1]
class Solution {
public:vector<int> countBits(int num) {vector<int> dp(num+1,0);for(int i = 1; i <= num; i++){if((i & 1) == 1)dp[i] = dp[i-1] + 1;else dp[i] = dp[i>>1];}return dp;}
};
一些补充
2021.7.4
剑指 Offer 56 - I. 数组中数字出现的次数
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
限制:
2 <= nums.length <= 10000
两个只出现依次的数字位x,y
x和y二进制至少有一位不同,根据此位可以将nums划分成分别包含x和y的两个子数组。
分别对两个子数组遍历执行异或操作,可以得到两个只出现一次的数字x,y。
1、遍历nums数组执行异或,得到结果 x^y
2、x^y某二进制位为1,则x和y的此二进制位一定不同。
初始化一个辅助变量m = 1,从右向左循环判断,可以得到x^y的首位1,记录在m中。
3、拆分nums为两个子数组
4、分别遍历两个子数组执行异或
for(num : nums)
if(num & m == 1) x ^= num;
else y ^= num;
return x,y;
最终代码:
vector<int> singleNumbers(vector<int>& nums)
{int ret = 0;for(int n : nums)ret ^= n; // x^yint div = 1;while((ret & div) == 0)div <= 1;int x = 0, y = 0;for(int n : nums){if(div & n == 0)a ^= n;else b ^= n;}return vector<int> {x,y};
}