面试题56 - I. 数组中数字出现的次数
https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/
算法思想
位运算,异或运算
思路
- 由于异或运算之后,相同的两个数都变成0这个特性。
- 采用两次遍历异或运算的方式。第一步异或,相同的数其实都抵消了,剩下两个不同的数。这两个数异或结果肯定有某一位为1,不然都是0的话就是相同数。找到这个位,不同的两个数一个在此位为0,另一个为1。按此位将所有数分成两组,分开后各自异或,相同的两个数异或肯定为0(而且分开的时候,两个数必为一组)。剩下的每组里就是要找的数。
代码实现
public class SingleNumbers {
public int[] singleNumbers(int[] nums) {
int sum = 0;
for (int num : nums) {
sum ^= num;
}
int firstO = 1;
while ((sum & firstO) == 0) {
firstO <<= 1;
}
int a = 0;
int b = 0;
for (int num : nums) {
if ((num & firstO) == 0) {
a ^= num;
} else {
b ^= num;
}
}
return new int[]{a,b};
}
}
复杂度
时间复杂度是O(N): 两次便利nums数组。
空间复杂度O(1):只使用了常数个临时变量。
位运算补充知识
- 左移或者右移运算(计算m*2^n)
- 左移是乘以2的幂,对应着右移则是除以2的幂
- 任何数左移(右移)32的倍数位等于该数本身。
- 在位移运算m<<n的计算中,若n为正数,则实际移动的位数为n%32,若n为负数,则实际移动的位数为(32+n%32),右移,同理。
- 判断一个数的奇偶性
n&1 == 1?”奇数”:”偶数”,若n的二进制最低位是1(奇数)与上1,结果为1,反则结果为0 - 不用临时变量交换两个数
nums[i]= nums[i]^nums[j];
nums[j] = nums[j]^nums[i];
nums[i] = nums[i]^nums[j];
异或的常用公式:b^(a^b)=a
,a ^b ^ c = a ^ (b ^ c) = (a ^ b) ^ c;
d = a ^b ^ c 可以推出 a = d ^ b ^ c.
,a ^ b ^a = b.
4. 取绝对值
先整理一下使用位运算取绝对值的思路:若a为正数,则不变,需要用异或0保持的特点;若a为负数,则其补码为源码翻转每一位后+1,先求其源码,补码-1后再翻转每一位,此时需要使用异或1具有翻转的特点。
任何正数右移31后只剩符号位0,最终结果为0,任何负数右移31后也只剩符号位1,溢出的31位截断,空出的31位补符号位1,最终结果为-1.右移31操作可以取得任何整数的符号位。
(a^(a>>31))-(a>>31)
那么综合上面的步骤,可得到公式。a>>31取得a的符号,
若a为正数,a>>31等于0,a^0=a,不变;
若a为负数,a>>31等于-1 ,a^-1翻转每一位.