• 【LeetCode-136、137、260】只出现一次的数字 [难度:简单、中等]


    136. 只出现一次的数字

    给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

    你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。

    136题解法

    如果不考虑时间复杂度和空间复杂度的限制,这道题有很多种解法,可能的解法有如下几种。

    • 使用集合存储数字。遍历数组中的每个数字,如果集合中没有该数字,则将该数字加入集合,如果集合中已经有该数字,则将该数字从集合中删除,最后剩下的数字就是只出现一次的数字。
    • 使用哈希表存储每个数字和该数字出现的次数。遍历数组即可得到每个数字出现的次数,并更新哈希表,最后遍历哈希表,得到只出现一次的数字。
    • 使用集合存储数组中出现的所有数字,并计算数组中的元素之和。由于集合保证元素无重复,因此计算集合中的所有元素之和的两倍,即为每个元素出现两次的情况下的元素之和。由于数组中只有一个元素出现一次,其余元素都出现两次,因此用集合中的元素之和的两倍减去数组中的元素之和,剩下的数就是数组中只出现一次的数字。

    如何才能做到线性时间复杂度和常数空间复杂度呢?

    答案是使用位运算。对于这道题,可使用异或运算 ⊕。异或运算有以下三个性质。

    • 任何数和 0 做异或运算,结果仍然是原来的数,即 a⊕0=a.
    • 任何数和其自身做异或运算,结果是 0,即 a⊕a=0。
    • 异或运算满足交换律和结合律,即 a⊕b⊕a=b⊕a⊕a=b⊕(a⊕a)=b⊕0=b.
     class Solution {
        public int singleNumber(int[] nums) {
            /**写法一:
                一个数和它本身做异或运算结果为 0:a ^ a = 0;
                一个数和 0 做异或运算的结果为它本身: a ^ 0 = a
             */
             /*
             int single = 0;
             for(int num:nums){
                 single ^=num;
             }
             return single;
             */
    	     //写法二:流
             return Arrays.stream(nums).reduce(0,(a,b)-> a^b);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    137. 只出现一次的数字 II

    给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。

    你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。

    137题解法

    由于数组中的元素都在 int(即 32 位整数)范围内,因此我们可以依次计算答案的每一个二进制位是 0还是 1。

    具体地,考虑答案的第 i 个二进制位(i从 0 开始编号),它可能为 0或 1。对于数组中非答案的元素,每一个元素都出现了 3 次,对应着第 i 个二进制位的 3个 0 或 3 个 1,无论是哪一种情况,它们的和都是 3 的倍数(即和为 0或 3)。因此:

    答案的第 i个二进制位就是数组中所有元素的第 i 个二进制位之和除以 3 的余数。

    这样一来,对于数组中的每一个元素 x,我们使用位运算 (x >> i) & 1 得到 x的第 i个二进制位,并将它们相加再对 3取余,得到的结果一定为 0 或 1,即为答案的第 i个二进制位。

     class Solution {
        public int singleNumber(int[] nums) {
            /**方法一:哈希表
            
    
            Map map = new HashMap();
            for(int num:nums){
                map.put(num,map.getOrDefault(num,0)+1);
            }
            int ans = 0;
            for(Map.Entry entry : map.entrySet()){
                if (entry.getValue()==1){
                    ans = entry.getKey();
                    break;
                }
            }
            return ans;
             */
             
            /**方法二:位运算
            
             */
            int ans = 0;
            for (int i = 0; i < 32; ++i) {
                int total = 0;
                for (int num: nums) {
                    total += ((num >> i) & 1);
                }
                if (total % 3 != 0) {
                    ans |= (1 << i);
                }
            }
            return ans;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    260. 只出现一次的数字 III

    给你一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。

    你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。

    260题解法

    方法一:哈希表
    我们可以使用一个哈希映射统计数组中每一个元素出现的次数。
    在统计完成后,我们对哈希映射进行遍历,将所有只出现了一次的数放入答案中。

    时间复杂度:O(n),其中 n是数组 nums的长度。
    空间复杂度:O(n),即为哈希映射需要使用的空间。

    方法二:位运算
    如何才能做到线性时间复杂度和常数空间复杂度呢?

    答案是使用位运算。对于这道题,可使用异或运算 ⊕。异或运算有以下三个性质。

    • 任何数和 0 做异或运算,结果仍然是原来的数,即 a⊕0=a.
    • 任何数和其自身做异或运算,结果是 0,即 a⊕a=0。
    • 异或运算满足交换律和结合律,即 a⊕b⊕a=b⊕a⊕a=b⊕(a⊕a)=b⊕0=b.

    假设数组 nums 中只出现一次的元素分别是 x1和 x2 。如果把 nums中的所有元素全部异或起来,得到结果 x,那么一定有:

                x=x1⊕x2
    
    • 1

    其中 ⊕ 表示异或运算。这是因为 nums 中出现两次的元素都会因为异或运算的性质 a⊕b⊕b=a抵消掉,那么最终的结果就只剩下 x1和 x2的异或和。

    x 显然不会等于 0,因为如果 x=0,那么说明 x1=x2,这样 x1和 x2就不是只出现一次的数字了。因此,我们可以使用位运算 x & -x取出 x 的二进制表示中最低位那个 1,设其为第 l 位,那么 x1 和 x2中的某一个数的二进制表示的第 l 位为 0,另一个数的二进制表示的第 l 位为 1。在这种情况下,x1⊕x2的二进制表示的第 l位才能为 1。

    这样一来,我们就可以把 nums中的所有元素分成两类,其中一类包含所有二进制表示的第 l 位为 0 的数,另一类包含所有二进制表示的第 l 位为 1的数。可以发现:

    对于任意一个在数组 nums中出现两次的元素,该元素的两次出现会被包含在同一类中;

    对于任意一个在数组 nums中只出现了一次的元素,即 x1和 x2 ,它们会被包含在不同类中。

    因此,如果我们将每一类的元素全部异或起来,那么其中一类会得到 x1
    ,另一类会得到 x2 。这样我们就找出了这两个只出现一次的元素。

    时间复杂度:O(n),其中 n 是数组 nums 的长度。
    空间复杂度:O(1)。

    class Solution {
        public int[] singleNumber(int[] nums) {
            /**
            方法一:哈希
            每个值作key,出现次数为value
            取value为1的key
            
            Map map = new HashMap();
            int[] ans = new int[2];
            for(int i=0;i entry : map.entrySet()){
                if(entry.getValue()==1){
                    ans[k++] = entry.getKey();
                }
            }
            return ans;
            */
    
            /**
            方法二:位运算
            异或运算的性质:a⊕a=0,a⊕0=a, a⊕b⊕b=a;
            因此,所有元素异或后:x=x1⊕x2
             */
             int x12 = 0;
             for(int num:nums){
                 x12 ^=num;
             }
    
             //防止溢出
             int lsb = (x12 == Integer.MIN_VALUE ? x12 : x12 &(-x12));
             int type1 = 0,type2=0;
             for(int num:nums){
                 if((num&lsb) !=0){
                     type1 ^=num;
                 }else{
                     type2 ^= num;
                 }
             }
             return new int[]{type1,type2};
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
  • 相关阅读:
    网络靶场实战-物联网安全qiling框架初探
    PDO:从 MySQL 中选择行
    DDoS如何防范?防御DDoS攻击的几大有效方法
    unity urp 实现泰森多边形Voronoi扰动
    解决WIFI网络登录困难的方法
    【超详细】7000字+24张图带你彻底弄懂线程池
    QDir类【官翻】
    Redis 异常三连环
    springBoot源码之servlet与reactive
    C/C++学生综合测评系统
  • 原文地址:https://blog.csdn.net/weixin_43431218/article/details/133869445