《算法零基础100讲》(第46讲) 位运算 (异或) 入门

Posted 英雄哪里出来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《算法零基础100讲》(第46讲) 位运算 (异或) 入门相关的知识,希望对你有一定的参考价值。

零、写在前面

  这是《算法零基础100讲》 专栏打卡学习的第四十六天了。
  每天打卡的题,做不出来没关系,因为困难的题涉及知识点较多,后面还是会开放出来的,就像昨天的 最大公约数 那道题今天还是会有,所以不要着急,内容能看懂,能自己分析,能做出简单题,就可以打卡。
  在刷题的过程中,总结自己遇到的坑点,写出 「 解题报告 」 供他人学习,也是一种自我学习的方式。这就是经典的帮助他人的同时,成就自己。目前, 「 万人千题 」 社区 每天都会有五六篇高质量的 「 解题报告 」 被我 「 加精 」。如果觉得自己有能力的,也可以来发布你的 「 解题报告 」。千万级流量,你我共同拥有。

一、概念定义

1、异或运算符的定义

  异或运算符是一个二元的位运算符,也就是有两个操作数,表示为x ^ y
  异或运算会对操作数的每一位按照如下表格进行运算,对于每一位只有 0 或 1 两种情况,所以组合出来总共 2 2 = 4 2^2 = 4 22=4 种情况。

左操作数右操作数结果
000
011
101
110

  通过这个表,我们得出一些结论:
  1)两个相同的十进制数异或的结果一定为零。
  2)任何一个数和 0 的异或结果一定是它本身。
  3)异或运算满足结合律和交换律。

#include <stdio.h>
int main() 
    int a = 0b1010;           // (1)
    int b = 0b0110;           // (2)
    printf("%d\\n", (a ^ b) ); // (3)
    return 0;

  • ( 1 ) (1) (1) 在C语言中,以0b作为前缀,表示这是一个二进制数。那么a的实际值就是 ( 1010 ) 2 (1010)_2 (1010)2
  • ( 2 ) (2) (2) 同样的,b的实际值就是 ( 0110 ) 2 (0110)_2 (0110)2
  • ( 3 ) (3) (3) 那么这里a ^ b就是对 ( 1010 ) 2 (1010)_2 (1010)2 ( 0110 ) 2 (0110)_2 (0110)2 的每一位做表格中的^运算。

  所以最后输出结果为:12。因为输出的是十进制数,它的二进制表示为: ( 1100 ) 2 (1100)_2 (1100)2

2、异或运算符的应用

1)标记位取反

【例题1】给定一个数,将它的低位数起的第 4 位取反,0 变 1,1 变 0。

  这个问题,我们很容易联想到异或。我们分析一下题目意思,如果第 4 位为 1,则让它异或上 0b1000就能变成 0;如果第 4 位 为 0,则让它异或上 0b1000就能变成 1,也就是无论如何都是异或上 0b1000,代码如下:

#include <stdio.h>
int main() 
    int x;
    scanf("%d", &x);
    printf("%d\\n", x ^ 0b1000); 
    return 0;

2)变量交换

【例题2】给定两个数 a a a b b b,用异或运算交换它们的值。

  这个是比较老的面试题了,直接给出代码:

#include <stdio.h>
int main() 
    int a, b;
	while (scanf("%d %d", &a, &b) != EOF) 
	    a = a ^ b;   // (1)
	    b = a ^ b;   // (2)
	    a = a ^ b;   // (3)
	    printf("%d %d\\n", a, b);
	
	return 0;

  我们直接来看 ( 1 ) (1) (1) ( 2 ) (2) (2) 这两句话,相当于b等于a ^ b ^ b,根据异或的几个性质,我们知道,这时候的b的值已经变成原先a的值了。
  而再来看第 ( 3 ) (3) (3) 句话,相当于a等于a ^ b ^ a,还是根据异或的几个性质,这时候,a的值已经变成了原先b的值。
  从而实现了变量ab的交换。

3)出现奇数次的数

【例题3】输入 n n n 个数,其中只有一个数出现了奇数次,其它所有数都出现了偶数次。求这个出现了奇数次的数。

  根据异或的性质,两个一样的数异或结果为零。也就是所有出现偶数次的数异或都为零,那么把这 n n n 个数都异或一下,得到的数就一定是一个出现奇数次的数了。

#include <stdio.h>
int main() 
    int n, x, i, ans;
    scanf("%d", &n);
    ans = 0;
    for(i = 0; i < n; ++i) 
        scanf("%d", &x);
        ans = (ans ^ x);
     
    printf("%d\\n", ans);
    return 0;

二、题目描述

  未知 整数数组 arrencodedSize+1个非负整数组成。经编码后变为长度为 encodedSize的另一个整数数组 encoded,其中 encoded[i] = arr[i] XOR arr[i + 1]。例如,arr = [1,0,2,1]经编码后得到 encoded = [1,2,3]。给你编码后的数组 encoded和原数组 arr的第一个元素 first(arr[0])。请解码返回原数组 arr。可以证明答案存在并且是唯一的。

三、算法详解

  首先,编码前的数组为未知数,并且有 encodedSize+1个,所以我们需要生成一个长度为 encodedSize+1的数组,将第一个元素赋值为first,即arr[0] = first,代表它是一个已知数。
  而encoded是一个长度为encodedSize的已知数组,并且满足encoded[i] = arr[i] XOR arr[i + 1],从而得到:

encoded[0] = arr[0] XOR arr[1];

那么,我们给等式两边都异或上arr[0],就得到了:

arr[1] = encoded[0] XOR arr[0];

从而计算出了arr[1]的值,同理,可以得到:

arr[2] = encoded[1] XOR arr[1];
arr[3] = encoded[2] XOR arr[2];
...
arr[i] = encoded[i-1] XOR arr[i-1];

递推求解即可。

四、源码剖析

int* decode(int* encoded, int encodedSize, int first, int* returnSize)
    int *arr = (int *)malloc( (encodedSize+1) * sizeof(int));
    int i, sum = 0;
    *returnSize = encodedSize + 1;
    arr[0] = first;
    for(i = 1; i < *returnSize; ++i) 
        arr[i] = (arr[i-1] ^ encoded[i-1]);
    
    return arr;

五、推荐专栏

💜《夜深人静写算法》💜
三)初等数论入门

六、习题练习

序号题目链接难度
1只出现一次的数字★☆☆☆☆
2颠倒二进制位★☆☆☆☆
3汉明距离★☆☆☆☆
4数组异或操作★☆☆☆☆
5汉明距离之和★☆☆☆☆
6解码异或后的数组★☆☆☆☆

以上是关于《算法零基础100讲》(第46讲) 位运算 (异或) 入门的主要内容,如果未能解决你的问题,请参考以下文章

《算法零基础100讲》(第49讲) 位运算 (右移)

《算法零基础100讲》(第48讲) 位运算 (左移)

《算法零基础100讲》(第44讲) 位运算 (位或) 入门

《算法零基础100讲》(第50讲) 位运算 (按位取反)

《算法零基础100讲》(第45讲) 位运算 (位或) 进阶

《算法零基础100讲》(第58讲) 前缀和 线性前缀和变形