位运算问题
异或 (^ )
只出现一次的数字I 136题:
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
分析:
根据 A ^ A=0 ; 0 ^ A=A 将整个数组俩俩异或 最后剩的就是只出现一次的元素
代码:
public int singleNumber(int[] nums) {
int a=0;
for(int i=0;i<nums.length;i++){
a=a^nums[i];
}
return a;
}
只出现一次的数字III 260题:
给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。
示例:
输入: [1,2,1,3,2,5]
输出: [3,5]
分析:
一次遍历不够,要两次。第一次得到两个单数的异或;第二次利用技巧将数组分成两个集合,两个集合都只有一个单数,其余数都成对。。。
代码:
public int[] singleNumber(int[] nums) {
int diff=0;
//第一趟遍历 和I相同 目的是求出所求的两个单身数的XOR
for (int num:nums) {
diff^=num;
}
diff&=-diff; //elegent!因为上一步后diff中肯定至少有一位为1
//这一步的目的是找到最靠右的那个1(当然不找最右的也一样)作为二趟遍历中的判断标志位 例如diff为:000100
int[] res={0,0}; //保存要找的那两个单身数的数组
//第二趟遍历
for (int num:nums){
if ((num&diff)==0){ /*判断是为了分成两个集合XOR 这两个集合中各自包含一个单身数
并且每个集合中除了那个单身数都是成对的(两个相同的数对于diff中的那一个标志位 一定也一样啊,所以一定会判断到一个集合中)
每个集合内全部XOR就能得到那个单身数
*/
res[0]^=num;
}else {
res[1]^=num;
}
}
return res;
}
只出现一次的数字II 137题:
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
分析:
这题参考博客上的做法 好理解一些: 考虑每个元素的为一个32位的二进制数,这样每一位上出现要么为1 ,要么为0。对数组,统计每一位上1 出现的次数count,必定是3N或者3N+1 次。让count对3取模,能够获得到那个只出现1次的元素该位是0还是1
还有一种简单的异或方法 discuss
代码:
public int singleNumber(int[] nums) {
int res=0;
for (int i=0;i<32;i++){
int mask=1<<i;
int count=0;
for (int j=0;j<nums.length;j++){
if ((nums[j]&mask)!=0){
count++;
}
}
if (count%3!=0){
res=res|mask;
}
}
return res;
}
求众数 169题:
给定一个大小为 n 的数组,找到其中的众数。众数是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在众数。
分析:
这题方法很多 排序,哈希表,摩尔投票,count bits,位运算。用count bits和137题差不多,也是32位,按位统计(比较慢)。
代码:
位运算:
public int majorityElement(int[] num) {
int ret = 0;
for (int i = 0; i < 32; i++) {
int ones = 0, zeros = 0;
for (int j = 0; j < num.length; j++) {
if ((num[j] & (1 << i)) != 0) {
++ones;
}
else
++zeros;
}
if (ones > zeros)
ret |= (1 << i);
}
return ret;
}
摩尔投票:
public int majorityElement(int[] nums) {
int major=nums[0],count=1;
for (int i = 1; i <nums.length ; i++) {
if (count==0){
major=nums[i];
count++;
}else if (major==nums[i]){
count++;
}else count--;
}
return major;
}
缺失数字 268题:
给定一个包含 0, 1, 2, ..., n 中 n 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。
示例:
输入: [9,6,4,2,3,5,7,0,1]
输出: 8
分析:
这题主要的方法有两种:哈希表 或者 位元算 。相比之下位运算的空间复杂度只有O(1),所以更好。
利用异或性质 A ^ B ^ B=A;
代码:
public int missingNumber(int[] nums) {
int res=nums.length;
for (int i = 0; i <nums.length ; i++) {
res=res^i^nums[i];
}
return res;
}
移位 (<< ; >> ; >>>)
颠倒二进制位 190题:
颠倒给定的 32 位无符号整数的二进制位。
示例:
输入: 43261596
输出: 964176192
解释: 43261596 的二进制表示形式为 00000010100101000001111010011100 ,
返回 964176192,其二进制表示形式为 00111001011110000010100101000000 。
分析:
右移位,每次取最低位;加到结果,结果左移位。(32bit)
代码:
public int reverseBits(int n) {
int res=0;
for (int i = 0; i <32 ; i++) {
res=res<<i+(n&1);
n=n<<1;
}
return res;
}
位1的个数 191题:
编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。
分析:
注意条件中的无符号整数条件。思路就是移位,&1,逐位统计。
还要注意这里的移位:右移有两种,一种无符号右移位>>>;一种有符号右移位>> 。区别在于移位后拿什么补空位,>>是哪符号位补(0或1),>>>拿0补
<< : 左移运算符,num << 1,相当于num乘以2
>> : 右移运算符,num >> 1,相当于num除以2
>>> : 无符号右移,忽略符号位,空位都以0补齐
代码:
public int hammingWeight(int n) {
int count=0;
while (n!=0) {
if ((n&1)!=0){
count++;
}
n=n>>>1;
}
return count;
}
数字范围按位与 201题:
给定范围 [m, n],其中 0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含 m, n 两端点)。
分析:
当m!=n时,从m到n中肯定存在一个奇数一个偶数,而 奇数&偶数 的最后一位为0,所以最后结果的最后一位一定为零。我们将m和n的结尾cut掉,再赋值给m和n,直到m==n ,最后再在m后边补上等于被cut掉的位数的0。
代码:
public int rangeBitwiseAnd(int m, int n) {
int i = 0; // i means we have how many bits are 0 on the right
while(m != n){
m >>= 1;
n >>= 1;
i++;
}
return m << i;
}
与 (&)
2的幂 231题:
给定一个整数,编写一个函数来判断它是否是 2 的幂次方。
示例:
输入: 1
输出: true
解释: 2^0 = 1
分析:
如果一个数(正数)是2的幂,那么它的二进制表示中一定只有一个1。所以可以统计二进制中1的个数来判断,但更简单的是 n&(n-1)看结果是否为0。
代码:
public boolean isPowerOfTwo(int n) {
if(n<=0) return false;
if((n&(n-1))==0){
return true;
}
else return false;
}