剑指 Offer II 004. 只出现一次的数字 (137. 只出现一次的数字 II)

题目:

思路:

【1】使用哈希辅助空间的简单处理,记录出现的次数,然后把次数为1的提取出来。

【2】转化为位数统计,因为转化成位数只有只有1和0,如果有1即代表出现一次,所以除以对应的次数得到的余数便是剩下的那个数字留下的。【这种和哈希辅助空间都是算是可以通用的,即使条件变化了,对应修改的依旧是次数,而次数其实可以提取出来作为通用变量】

【3】最优的DFA( Deterministic Finite Automaton 即确定有穷自动机),从一个状态通过一系列的事件转换到另一个状态。如当时出现两个的时候,利用相同数异或为 0 的性质,可以实现状态切换:

当出现三个的时候,明显是需要标记多一个状态

 

举例子
以【10,10,10,100】为例
当第一次进入的时候
x = 10,所以 one ^ x 这部分为 10 而 ~two (由于two为0,所以得到-1)
故10&-1 = 10(相当于00001010&11111111 = 00001010)
为什么-1是11111111,要记住存的是补码,所以补码转为原码即:
11111111 - 1 = 11111110 符号位不变,其他变则为 10000001 (这就是-1)
所以后面two ^ x 部分也为10 ,而此时~one (由于上面已经变为了10,故的到-11)
所以10&-11 = 0 (相当于00001010&11110101)

第二次的时候
x = 10,所以 one ^ x 这部分为 10^10 = 0 而 ~two = -1
故0&-1 = 0
所以后面two ^ x 部分也为0^10 = 10 ,此时~one = -1
所以10&-1 = 10

第三次的时候
x = 10,one ^ x 这部分为 0^10 = 10 而 ~two = -11
故0&-1 = 0
所以后面two ^ x 部分也为10^10 = 00 ,此时~one = -1
所以0&-1 = 0

重点理解非运算:
比如:~37
在Java中,int 占四个字节,一个字节占8bit位,就是32位,32bit.
37转为二进制是100101.
计算机保存的都是补码,37是正数,他的原码,反码,补码都是一样的;
37在计算机中的补码:
00000000 00000000 00000000 00100101

~37的计算结果:
第一步:求37在计算中补码的相反码(不是反码),这里还是补码只是相反的补码
11111111 11111111 11111111 11011010
第二步:将相反的补码,转换为原码,由于相反码的第一个位是1,这个数是负数,负数的补码转反码 减一(-111111111 11111111 11111111 11011010 -1
= 
11111111 11111111 11111111 11011001
反码转原码:符号位不变,其他数相反
10000000 00000000 00000000 00100110
二进制100110十进制38
符号位不变-38

代码展示:

DFA的代码:

//时间0 ms击败100%
//内存41.4 MB击败11.19%
//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
    public int singleNumber(int[] nums) {
        int one = 0, two = 0;
        for(int x : nums){
            one = one ^ x & ~two;
            two = two ^ x & ~one;
        }
        return one;
    }
}

 

位数统计的代码:

//时间4 ms击败42.45%
//内存41.3 MB击败14.99%
//时间复杂度:O(n),针对常量的遍历是可以忽略的
//空间复杂度:O(1),虽然开辟了数组,但是由于是固定不变的就会认为是常量
class Solution {
    public int singleNumber(int[] nums) {
        int[] cnt = new int[32];
        for (int x : nums) {
            for (int i = 0; i < 32; i++) {
                if (((x >> i) & 1) == 1) {
                    cnt[i]++;
                }
            }
        }
        int ans = 0;
        for (int i = 0; i < 32; i++) {
            if ((cnt[i] % 3 & 1) == 1) {
                ans += (1 << i);
            }
        }
        return ans;
    }
}

 

使用哈希辅助空间的代码:

//时间5 ms击败37.21%
//内存41.1 MB击败41.76%
//时间复杂度为O(N)
//空间复杂度为O(N),毕竟使用了辅助空间来记录全部数据的个数
class Solution {
    public int singleNumber(int[] nums) {
        HashMap<Integer, Integer> hashMap = new HashMap<>();
        int count = 0;
        for (int i = 0; i < nums.length; i++){
            count = hashMap.getOrDefault(nums[i],0) + 1;
            hashMap.put(nums[i],count);
        }

        for (Integer key : hashMap.keySet()){
            if (hashMap.get(key) == 1){
                return key;
            }
        }
        return 0;
    }
}

 

posted @ 2023-01-31 17:48  忧愁的chafry  阅读(14)  评论(0编辑  收藏  举报