【算法】只出现一次的数字

1. leetcode136 

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

要求时间复杂度O(n),空间复杂度O(1)。

示例:输入[4, 1, 2, 1, 2],输出4

(1)面试官不想要的答案:建字典、排序。

(2)面试官想要的答案:位运算。

思路:如果我们对 0 和二进制位做 XOR 运算,得到的仍然是这个二进制位,a^0=a

如果我们对相同的二进制位做 XOR 运算,返回的结果是 0,a^a=0

XOR 满足交换律和结合律,a^b^a=a^a^b=0^b=b(位运算都满足交换律)

所以我们只需要将所有的数进行 XOR 操作,得到那个唯一的数字。

class Solution(object):
    def singleNumber(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        a = 0
        for i in nums:
            a ^= i
        return a

2. leetcode137

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

示例:输入: [0,1,0,1,0,1,99],输出: 99

思路:表面上,上面那题是两个相等的数取异或得到了0,就剩下了只出现一次的数。实际上是1+1=0也就是二进制运算只取了一位。所以如果要三个数位运算后为0,就是1+1+1=0,也就是三进制。

从位运算角度看,要求达到:01?01=10,10?01=00,所以可得01^01=10, 10&01=00。

使用a,b分别记录2位。

b = b xor x & ~a;
a = a xor x & ~b;

但是我并不能直接看出上面的状态转移方程。所以,参考评论某大佬的做法,可以使用卡诺图分析。

abxnew_anew_b
00101
01110
10100
00000
01001
10010

a的卡诺图:

x\ab00011110
000x1
101x0

b的卡诺图:

x\ab00011110
001x0
110x0

有:

(~x&a&b)|(~x&a&~b)=~x&a        

(x&~a&b)|(x&a&b)=x&b

(~x&~a&b)|(~x&a&b)=~x&b

x&~a&~b

于是有

a=(~x&a)|(x&b)

b=(~x&b)|(x&~a&~b)

class Solution(object):
    def singleNumber(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        a=0
        b=0
        tmp=0
        for i in nums:
            tmp=a
            a=((~i)&a)|(i&b)
            b=((~i)&b)|(i&(~tmp)&(~b))
        return b

由于代码的实现中,每一位的计算并不是并行进行的,所以可以通过画一个行列由a,new_b,x构成的卡诺图来利用先生成的位。

得到b=(~x&b)|(x&a)

 

只出现一次数字通解:

统计所有数字中每个位中1出现的总数,那么对于某个位,1出现的次数一定是3的倍数或者是1或者0,那么对这个数%3得到的结果就是目的数字在该位上的值。

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ans = 0;
        for (int i = 0; i < 32; i++) {  // 确定每一位
            int sum = 0;
            for (int num : nums) {
                sum += (num >> i) & 1;  // 统计当前位置有多少个元素
            }
            ans ^= (sum % 3) << i;   // 还原到第i位
        }
        return ans;
    }
};

3. leetcode260

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

示例:

把原数组分成两组,只出现过一次的两个数字分别在两组里边,那么问题就转换成之前的老问题了,只需要这两组里的数字各自异或,答案就出来了。

那么通过什么把数组分成两组呢?

放眼到二进制,我们要找的这两个数字是不同的,所以它俩至少有一位是不同的,所以我们可以根据这一位,把数组分成这一位都是 1 的一类和这一位都是 0 的一类,这样就把这两个数分到两组里了。

那么怎么知道那两个数字哪一位不同呢?

回到我们异或的结果,如果把数组中的所有数字异或,最后异或的结果,其实就是我们要找的两个数字的异或。而异或结果如果某一位是 1,也就意味着当前位两个数字一个是 1 ,一个是 0,也就找到了不同的一位。

思路就是上边的了,然后再考虑代码怎么写。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值