Leetcode只出现一次的数字

  1. Leetcode136.只出现一次的数字
  2. Leetcode137.只出现一次的数字Ⅱ
  3. Leetcode260.只出现一次的数字Ⅲ

Leetcode136.只出现一次的数字

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

解题思路

1.利用HashMap,对数组进行遍历,将数组元素添加到Map中去,再对Map遍历,找到只出现一次的元素。时间和空间复杂度都是O( n )。

2.利用HashSet,对数组进行遍历,新出现的元素添加到Set中,如果出现重复元素则删除Set中对应的元素,最后Set中剩余的元素就是只出现一次的数字。相比使用Map,使用Set的时间和空间复杂度都更优,但还是O( n )。

3.巧妙利用异或的特性:n ^ n = 0, n ^ 0 = n,所以对数组元素进行全员异或,相同的元素对异或结果都为零,最后的结果即为只出现一次的元素与零异或,结果为只出现一次的数字。时间复杂度为O( n ),空间复杂度为O( 1 )。

思路1和2的代码省去,这里只列出思路3的代码。后续的两道扩展题我们也只采用异或的思路解题。

代码

public int singleNumber(int[] nums) {
    int res = 0;
    for(int n : nums){
        res = res ^ n;
    }
    return res;
}

Leetcode137.只出现一次的数字Ⅱ

题目:给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。

解题思路:这道题和前一题的区别在于,重复数组出现的次数是三次,即奇数次,这就导致如果我们进行重复数字进行异或的话,结果不等于零而等于重复数字本身,就不能采用全员异或的方法了。

对于重复数字出现奇数次的情况,如果对这组数字对应的二进制位进行相加,可以得出一个结论,相加的结果每个二进制位上的数字都是3的整数倍

所以对于全部相加后最后的结果,每个二进制位的数字对3取余,得到的就是只出现一次的数字的二进制位的值:

在这里插入图片描述

代码

public int singleNumber(int[] nums) {
    int res = 0;
    int[] bit = new int[32];			//创建一个长度为32的数组,储存二进制位相加的值
    for(int n : nums){					//遍历数组中的每个数字
        int bitNum = 1;
        for(int i = 31; i >= 0; i--){	//遍历一个数字的每个二进制位,得到其二进制位值
            if((bitNum & n) != 0){
                bit[i]++;
            }
            bitNum <<= 1;				
        }
    }
    for(int i = 0; i < 32; i++){		//遍历最后的数组,每个元素对3取余。再对应二进制位生成数字
        res = res << 1;					
        res += bit[i] % 3;
    }
    return res;
}

Leetcode260.只出现一次的数字Ⅲ

题目:给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。

解题思路:重复数字出现次数为偶数次,只出现一次的数字有两个,这样如果进行全员异或的话,结果是只出现一次的两个数字的异或值,并不是我们想要的结果。

如果我们把这个数组分成两组,两个只出现一次的数字分别分到不同的一组,而重复的数字分到同一组,这样分别对两组进行全员异或,每组异或得到的结果就是我们想要的那个数字了。

这样又引出一个问题,我们如何对这个数组进行分组?

思路是:先对全数组元素进行全员异或,取异或值的最低位次为1的值q。对于q的为1的位次,表示两个数字对应的位次一个为0,一个为1,这样如果这两个数字分别对q进行按位与操作,则一个为0,一个不为0,这样就实现了把这两个数字分到不同两组的条件;而对于重复的数字,对应的二进制位的值相同,与q的按位与结果也是相同的,也实现了将重复数字分到相同组的条件。

在这里插入图片描述

代码

public int[] singleNumbers(int[] nums) {
    int res = 0;
    for(int n : nums){			//遍历数组进行全员异或
        res ^= n;
    }
    //int q = res & (-res);		//简便方法:res & (-res)的作用是得到最低位次的1
    int q = 1;
    while((q & res) == 0){
        q = q << 1;
    }
    int a = 0, b = 0;
    for(int n : nums){			//遍历数组进行分组
        if((n & q) == 0){		//按位与结果为0的分成一组,再进行全员异或
            a ^= n;
        }else{					//按位与结果不为0的分成一组,再进行全员异或
            b ^= n;
        }
    }
    return new int[]{a,b};
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值