面试题: 找出数组中3个只出现1次的数(Python实现)

1. 先考虑此题:找出数组中1个只出现1次的数,其余数均出现2次
解:全部数进行亦或^ ,出现两次的数,亦或后为0,最后结果就是只出现一次的数

2. 再考虑:找出数组中2个只出现1次的数,其余数均出现2次
解:设这两个数为a,b。x=a xor b, 因为 a!=b,有:x != 0 一定成立。数组全部数进行亦或,最后结果就是 x(出现两次的数,亦或后为0)。
设 t=x & (-x),于是t 就是x 里最低一位为1的(-x 按照补码存储,即x按位取反加1)。(例如若x=0000 1010,则 t = 0000 0010 。)t 代表a,b里不一致的最低位(即,如果a 在这一位上为1,那么b一定在这一位上为0,因为a,b亦或的结果在这一位上为1),注意 t 上有且只有1位为1
求出 t是为了把原数组分成两个部分,使得a,b恰好分别在两个部分里,从而把问题转化为第一问:找出数组里1 个只出现1次的数,其余数出现两次。
接下来的思路:用 t 和原数组每一个元素与操作(&),如果结果为0,说明当前元素在t 不为0 的那个位上,的值为0,反之,结果不为0,则代表当前元素在t 不为0 的那个位上,的值为1。这样把数组元素分成两堆,由之前推理知道,a,b一定在这个位上,一个为0,一个为1,这样a,b分别在两堆。
做法:创建新变量 num1=0,num2=0
用 t 和原数组每一个元素与操作(&),如果结果为0,该元素与num1亦或,反之,结果不为0,该元素与num2亦或。最后num1,num2就是要找出的两个只出现一次的数

3. 找出数组中3个只出现1次的数,其余数均出现2次

思路:把问题转化为第1问、第2问。原数组通过一定方法分成两个数组,让这三个不同的数恰好能1个进入一个数组,另外2个进入另一个数组,再用第一问、第二问的方法求解。

注意:1.分好后的两个数组,一定是:进入了1个出现一次的数的数组,元素个数为奇数个,进入了2个出现一次的数的数组,元素个数为偶数个。2. 这三个数亦或,结果可能为0,例如1,2,3,也可能结果不为0,但这三个数中取出两个亦或,结果一定不为0(因为这两个数不可能相同)。

设这三个只出现1次的数分别为a,b,c。因为a,b,c互不相等,于是,对三个数从最低位开始考察,这三个数在该位上值可能分别为:
0,0,0 或 0,0,1 或 0,1,0或 0,1,1或
1,0,0 或 1,0,1 或 1,1,0或 1,1,1。
注意到,除了0,0,0和 1,1,1这两种情形,其余情况都能把这三个数分成两堆。
假设 考察最低位就已经把三个数分成两堆了,那么问题就已经被转化为第一问、第二问了,调用解决第一问、第二问的函数就能求解。若没有分成两堆,说明三个数在这一位上相同,那么此时t 左移一位(t<<1),此时
t=0000 0010,也就是对第二位重复上面这个判断过程。三个数不可能每一位都相同,不断循环左移t,直到能分成两堆为止。

这种分数组的具体实现是:设原数组为 l ,新数组为l1,l2,用count1,count2 对 l1,l2 元素个数计数,xor1,xor2分别记录l1,l2里所有元素的亦或结果。初始值:xor1 = xor2 = count1 = count2 = 0

令 t=1,注意此时 t 的二进制是0000 0001,于是让 l 里每个元素和 t 进行&与操作,
若结果为0,说明当前元素在t不为0的这位上,值为0,把它当成 l1元素,xor1=xor1^当前元素,count1+=1;
若结果为1,说明当前元素在t不为0的这位上,值为1,xor2=xor2^当前元素,count2+=1。
循环结束后,查看count为偶数个的那一堆,假设现在count1为偶数(若count2为偶数,同理)如果 xor1!=0,则说明分堆成功,l1里面恰好存在两个只出现一次的数,l2里面恰好存在一个只出现一次的数,且xor2值就是这个只出现一次的数。向原数组添加xor2,则xor2这个只出现一次的数现在出现了2次,接下来按照 问题2 思路求解就行。
如果 xor1==0,说明l1里面全是出现了两次的数,分堆失败(注意,出现一次的数有三个,这三个数不可能都在这个偶数堆里,否则这不是偶数堆这是奇数堆)。此时t右移一位,继续对第二位进行判断分堆,直到能分堆成功为止。

原创文章,作者rolling_kitten,首次发表于csdn,转载请注明出处
python代码实现:

def find_two_number(num_list,two_number_xor,one_number):
    num_list.append(one_number)
    # 分成两堆
    split_flag = two_number_xor & -two_number_xor
    print(split_flag)
    num1, num2 = 0, 0
    for i in num_list:
        if split_flag & i:
            num1 ^= i
        else:
            num2 ^= i

    # if split_flag&one_number:  #这里是不加append时,使用这种方法
    #     num1^=one_number
    # else:
    #     num2 ^=one_number
    print('出现1次的另外的两个数分别是 {} {}'.format(num1, num2))


def find_three_number(num_list):
    split_flag = 1
    xor_value1 = xor_value2 = count1 = count2 = 0
    while True:
        #首先是拿1和所有的数进行按位与
        for i in num_list:
            if i & split_flag:
                count1 += 1
                xor_value1 ^= i
            else:
                count2 += 1
                xor_value2 ^= i
        #如何判断分开了
        if count1%2==0 and xor_value1!=0:
            print('出现1次的第一个数 {}'.format(xor_value2))
            find_two_number(num_list,xor_value1,xor_value2)
            break
        if count2%2==0 and xor_value2!=0:
            print('出现1次的第一个数 {}'.format(xor_value1))
            find_two_number(num_list, xor_value2, xor_value1)
            break
        split_flag=split_flag<<1

if __name__ == '__main__':
    num_list = [8, 5, 6, 4, 8, 6, 5, 7, 11]
    find_three_number(num_list)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值