【算法基础】基于异或的排序、基于异或的经典面试题

1. 传统交换

传统交换方法如下:

def swap(i, j):
    tmp = i
    i = j
    j = tmp

通过开辟一个额外的变量空间,承载其中的一个数,以实现变量的交换

2. 异或与异或的规律

异或定义很简单,两数相同则为0,两数相异则为1
异或满足的性质包括结合律和交换律:

  • 性质1:0^a=a(0与任意数结果取决于任意数),a^a=0(任意数与其自身异或结果为0)
  • 性质2:a^b=b^a(交换律),(a^b)^c=a^(b^c)(结合律)
  • 性质3:基于以上两个性质可以推出,多个数进行异或,无论怎样排序,异或的结果不变

3. 基于异或的排序

基于异或排序的方法如下:

def swap2(i, j):
    i = i ^ j
    j = i ^ j
    i = i ^ j

为什么这样的方法能够奏效,可以看下面的例子:假设i=甲,j=乙
经第一句i = i ^ j后,i=甲^乙,j=乙
经第二句j = i ^ j后,i=甲^乙,j=(甲^乙)^乙=甲^(乙^乙)=甲^0=甲(基于性质1和性质2的结合律)
经第三句i = i ^ j后,i=(甲^乙)^甲=(甲^甲)^乙=0^乙=乙(基于性质1和性质2)
从而完成两数交换

4. 需要注意的地方

在写排序时候我们通常需要做交换操作,交换数组中的两个数。如选择排序中我们需要将子数组中最小的数与当前第一位数字交换;又如冒泡排序中我们需要通过两两交换来将最大的数交换到子数组的最高位。
但这时候,当ij指向数组的同一个内存区域时,交换会失败! 也就是说我们想要交换arr[i]arr[j],而此时i==j时交换会失败,因为当指向同一片内存区域时,代码就类似于变成了:

def swap2(i):
    i = i ^ i
    i = i ^ i
    i = i ^ i

自身与自身的相与结果为0,所以这样的操作会将原本数组中的数抹成0,而不是保留原数!
若只是交换i、j的值(ij不指向同一片内存区域)则可以成功,ij的值相等也不会被抹成0。听到的一个说法是,原理是靠内存地址来异或的。

5. 经典面试题1

5.1 题目

int [] arr中,有一种数出现了奇数次,其他数出现了偶数次,请找出这种奇数次的数。

5.2 思路

通过异或解决,因为同样的数与自身相异或结果为0,偶数条件下也为0(异或的性质1);而出现奇数次的数与0相与的结果为数的本身。所以只需要将所有的变量异或上就行了。

5.3 实现

6. 经典面试题2

6.1 题目

int [] arr中,有两种数出现了奇数次,其他数出现了偶数次,请找出这两种出现了奇数次的数

6.2 思路

通过5中的题目可以知道,通过将本题arr中所有数异或,得到的结果应该为a_xor_b = a^b
如何从a_xor_b = a^b中分离出其中的数是值得思考的问题
考虑的方向是,既然a和b是两个不相等的数,那么a^b的结果一定存在1(从二进制上考虑,a^b的二进制中肯定存在1)
那么这个1就可以作为突破的方向,假设我们从a^b中找到第2位上的数为1,则证明a二进制的第2位和b二进制的第2位是不相等的,一个为0,另一个为1(a可能为0/1,b同理)
此时arr中的数可以分为两类,一类是在第2位上为1的,另一类是在第2位上为0的,而a和b肯定是分属两类
通过将所有第2位上为1的数进行异或,或将所有第2位上为0的数进行异或,得到的肯定是a和b的其中一个。因为a和b分别属于这两个类中出现奇数次的数,其他偶数次的在异或过程中已经消为0了。
找到了其中一个数后,通过将a_xor_b = a^b再与找到的数(可能是a也可能是b)进行异或,得到的就是另外一个。

6.3 实现

# 在int [] arr中,有两种数出现了奇数次,其他数出现了偶数次,请找出这两种出现了奇数次的数

if __name__ == '__main__':
    arr = [6, 6, 7, 8, 5, 4, 7, 4, 5, 3]
    print("原数组为:", arr)

    a_xor_b = 0
    for num in arr:
        a_xor_b = a_xor_b ^ num
    print("a^b=", a_xor_b)

    # a_xor_b=a^b,既然a和b是两个不同的数,a_xor_b中一定存在某一位为1
    right_one = a_xor_b & (~a_xor_b + 1)  # 提取出最右侧的1,是常见的操作

    only_one = 0
    for num in arr:
        if num & right_one == right_one:
            only_one = only_one ^ num
    print("其中一个数为:", only_one)
    print("另一个数为:", a_xor_b ^ only_one)
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值