[Leedcode][JAVA][面试题56 - I][第260题][位运算][HashSet]

【问题描述】 [面试题56 - I] [数组中数字出现的次数]

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

 

示例 1:

输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]


【解答思路】

1.位运算

【1,4,4,6】

  • 根据异或的性质,相同异或结果为 0,相异的异或结果为 1,所以将数组中的数字做异或运算,得到两个只出现一次数字的异或结果。
    1 ^ 4 ^ 4 ^ 6 = 7


    image.png
  • 由于两数字不同,异或结果不为 0,则其二进制中必然有一位是不相同的。
    方法一: 7 & i = 0 (出现0 证明可以作为分组依据 )



    方法二:7 & -7 = 0001


    image.png
  • 可以先找到不相同的位,就可以将数组分成两组,再像之前那样做异或运算。


    根据最后一位分组
class Solution {
    // 自己寻找最右边 1 的位置
    public int[] singleNumbers(int[] nums) {
        if (nums == null || nums.length < 2) return new int[0];
        int xor = 0; // 数组第一次异或的结果,即两个只出现一次的数的异或结果
        for (int num : nums) xor ^= num;
        // 寻找右边第一个 1,也就是最右边不相同的位
        int i = 1;
        while ((i & xor) == 0){
            i <<= 1;
        }
        int[] res = new int[2];
        for (int num : nums) {
            if ((i & num) == 0) res[0] ^= num;
            else res[1] ^= num;
        }
        return res;
    }


    // 位运算 xor & (-xor)
    public int[] singleNumbers(int[] nums) {
        if (nums == null || nums.length < 2) return new int[0];
        int xor = 0; // 表示两个只出现一次的数字的异或结果
        for (int num : nums) xor ^= num;

        xor &= -xor;
        int[] res = new int[2];
        for (int num : nums) {
            if ((xor & num) == 0) res[0] ^= num;
            else res[1] ^= num;
        }
        return res;
    }
}


2. HashSet

遍历数组,遇到的数如果 HashSet 中存在,就把这个数删除。如果不存在,就把它加入到 HashSet 中。最后 HashSet 中剩下的两个数就是我们要找的了。

时间复杂度:O(N) 空间复杂度:O(N)

public int[] singleNumber(int[] nums) {
    HashSet set = new HashSet<>();
    for (int n : nums) {
        if (set.contains(n)) {
            set.remove(n);
        } else {
            set.add(n);
        }
    }
    int[] result = new int[2];
    int i = 0;
    for (int n : set) {
        result[i] = n;
        i++;
    }
    return result;
}

【总结】

1. 位运算
异或运算(^)
运算规则:0^0=0;  0^1=1;  1^0=1;   1^1=0;
即:参加运算的两个对象,如果两个相应位为“异”(值不同),则该位结果为1,否则为0。
5 ^ 1  = 0101 ^ 0001 = 0100 = 4
5 ^ 3  = 0101 ^ 0011 = 0110 = 6
用法
1. 翻转指定位 对应位异或1
X=10101110,使X低4位翻转,用X ^0000 1111 = 1010 0001即可得到。
2.两个数是否相等 ==0
 5 ^ 5  = 0101 ^ 0101 = 0000 = 0

与运算(&)
运算规则:0&0=0;  0&1=0;   1&0=0;    1&1=1;
即:两位同时为“1”,结果才为“1”,否则为0
5 & 1 = 0101 & 0001 = 0001 = 1
5 & 2 = 0101 & 0010 = 0000 = 0
用法 
取指定位   对应位与1
设X=10101110,
取X的低4位,用 X & 0000 1111 = 00001110 即可得到;

或运算(|)
运算规则:0|0=0;  0|1=1;  1|0=1;   1|1=1;
即 :参加运算的两个对象只要有一个为1,其值为1
用法:
指定位置置1  对应位或1
将X=10100000的低4位置1 ,用X | 0000 1111 = 1010 1111即可得到

取反运算(~)
运算规则:~1=0;  ~0=1;
即:对一个二进制数按位取反,即将0变1,1变0

带符号左移运算(<<)
若左移时舍弃的高位不包含1,则每左移一位,相当于该数乘以2(右边补0)
3 << 1 = 0011 <<1 = 0110 = 6
带符号右移运算(>>)
正数操作数每右移一位,相当于该数除以2
(左补0 or 补1得看被移数是正还是负)
5 >> 1 = 0101 >> 1 = 0010 = 2
5 >> 2 = 0101 >> 2 = 0001 = 1
 -14 >> 2 = 11110010 >> 2 = 11111100 = -4
无符号右移运算(>>>) 
5 >>> 1 = 0101 >>> 1 = 0010 = 2
 -14 >>>2 =11111111 11111111 1111111111110010  >>>2 = 00111111 11111111 1111111111111100 =  1073741820
移位总结
- 正数的左移与右移,负数的无符号右移,就是相应的补码移位所得,在高位补0即可。
- 负数的右移,就是补码高位补1,然后按位取反加1即可。
2. 位运算 判相等异或^ 取位与&1 置位或|1
3. HashMap 或 HashSet常见用法

** 3.1 ** HashSet
(1)增加
public boolean add(E e);
(2)删除
public boolean remove(Object j);
(3)对比查找
public boolean contains(Object j);
(4)清空集合
public void clear();
(5)获取长度
public int size();
** 3.2 **HashMap
(1) 插入键值对数据
public V put(K key, V value)
(2)根据键值获取键值对值数据
public V get(Object key)
(3)获取Map中键值对的个数
public int size()
(4)判断Map集合中是否包含键为key的键值对
public boolean containsKey(Object key)
(5)判断Map集合中是否包含值为value的键值对
boolean containsValue(Object value)
(6)判断Map集合中是否没有任何键值对
public boolean isEmpty()
(7)清空Map集合中所有的键值对
public void clear()
(8)根据键值删除Map中键值对
public V remove(Object key)

你可能感兴趣的:([Leedcode][JAVA][面试题56 - I][第260题][位运算][HashSet])