位运算骚操作 Part 3
▶ 异或运算 "^" 具有的部分性质:
● 交换律,结合律
● a ^ b == (!a & b) | (a & !b),a ^ 1 == !a,a ^ 0 == a,a ^ a == 0,a ^ !a == 1,
● RAID 5 的理解:写入时,数据 A 写入硬盘 0, 数据 B 写入硬盘 1,数据 A^B 写入硬盘 2;读取时,数据 A 可用硬盘 1 和硬盘 2 的数据 B^C 进行验校或恢复
▶ 使用异或运算找非重复元素的方法,参考【https://blog.csdn.net/u013609078/article/details/51622077】,但是原文写的像屎一样,很多说法连良定义都没有
●(1)整型数组中有 1 个元素只出现一次,其他数字都出现两次,要求找出这个数字
■ 把所有元素进行规约异或即可
●(2)整型数组中有 2 个元素只出现一次,其他数字都出现两次,要求找出这个数字
■ 核心思想是利用两元素的差异(二进制表示中从低位到高位首个不同的位)作为条件来构造子列,把所有与 b 具有相同条件的元素去掉,在该子列中找 a 就等价于问题(1)
■ 找出 a 后 b = (a ^ b) ^ a
■ 代码
#include <iostream>
#include <vector>
#include <cassert> using namespace std; inline int getLastOneBit(int num) // 求 num 最低 "1" 位,返回值有且仅有一位为 1,特例输入 0 返回 0
{
return num & ~(num - );
} vector<int> findTwo(vector<int> & haha)
{
int ab = ;
for (int i = ; i < haha.size(); i++) // 第一轮得到 a ^ b,用 ab 来表示
ab ^= haha[i];
assert(ab != ); // 保证 a != b
int lastOneBit = getLastOneBit(ab); // 取 ab 的最低 "1" 位(定义为从低位到高位,首个至少有一个值为 1 的位),即 a 和 b 二进制表示中从低位到高位首个不同的位
// 如 ab = 01100100,则 lastOneBit = 00000100,可能的情况 a = ...100,b = ...000
int a = ;
for (int i = ; i < haha.size(); i++) // 不妨设 a 在 lastOneBit 位上为 1,第二轮取所有该位为 1 的元素作为子列进行异或运算,重复元素不影响结果
{
if (haha[i] & lastOneBit)
a ^= haha[i];
}
int b = ab ^ a;
cout << "findTwo -> a: " << a << ", b: " << b << endl;
return vector<int>({ a, b });
} int main()
{
vector<int> haha = { , , , , , , , };
vector<int> result = findTwo(haha); cout << "result -> a: " << result[] << ", b: " << result[] << endl;
return ;
}
■ 输出结果
findTwo -> a: , b:
result -> a: , b:
●(3)整型数组中有 3 个元素只出现一次,其他数字都出现两次,要求找出这个数字
■ 引理:若x ^ y ^ z == 0,则 x,y,z 最低 "1" 位有且仅有两个数为 1,如 x == 00001000,y == 00000100,z == 00001100 满足该式
证明:3 个数的最低 "1" 位四种情况:
① 全相同,该位 1 ^ 1 ^ 1 == 1;
② 全不同,该位 1 ^ 0 ^ 0 == 1;
③ 两个 0 一个 1,该位 1 ^ 0 ^ 0 == 1;
④ 两个 1 一个 0,该位 1 ^ 1 ^ 0 == 0,仅这种情况满足上式
■ 注意到恒等式 (a ^ b) ^ (a ^ c) ^ (b ^ c) == ((a^b^c) ^ c) ^ ((a^b^c) ^ b) ^ ((a^b^c) ^ a) == 0,所以 (a ^ b),(a ^ c),(b ^ c) 三个数的的最低 "1" 位有且仅有两个 1,
不妨设 (a ^ c) 和 (b ^ c) 该位为 1,(a ^ b) 的该位为 0,则 (a ^ c) 与 (b ^ c) 的最低 "1" 位相同,且 (a ^ b) 的最低 "1" 位不同,
于是 getLastOneBit(a ^ c) ^ getLastOneBit(b ^ c) ^ getLastOneBit(a ^ b) == getLastOneBit(a ^ b),干掉了最低 "1" 位相同的那两个,
类似问题(2),我们构造原数组的一个子列,筛选出原数组中满足 getLastOneBit((a^b^c) ^ x) == getLastOneBit(a ^ b) 的那些元素 x,
① 若 x 是重复元素,则 x 会在子列中出现零次或两次,不影响异或的结果;
② 若 x == a || x == b,则上述等式不成立,x 不在子列中;
③ 若 x == c,则上述等式成立,x 在子列中
所以在该子列中找 c 就等价于问题(1),然后把找到的 c 添加到原数组末尾(相当于新子列中 c 为重复元素),在该新数组中找 a 和 b 就等价于问题(2)
■ 代码
#include <iostream>
#include <vector>
#include <cassert> using namespace std; inline int getLastOneBit(int num)
{
return num & ~(num - );
} vector<int> findTwo(vector<int> & haha)
{
int ab = ;
for (int i = ; i < haha.size(); i++)
ab ^= haha[i];
assert(ab != );
int lastOneBit = getLastOneBit(ab); int a = ;
for (int i = ; i < haha.size(); ++i)
{
if (haha[i] & lastOneBit)
a ^= haha[i];
}
int b = ab ^ a;
cout << "findTwo -> a: " << a << ", b: " << b << endl;
return vector<int>({ a, b });
} int findOne(vector<int> & haha)
{
int abc = ;
for (int i = ; i < haha.size(); i++) // 第一轮得到 a ^ b ^ c,用 abc 表示
abc ^= haha[i]; int lastOneBit = ;
for (int i = ; i < haha.size(); i++) // 第二轮得到 getLastOneBit(a ^ b) ^ getLastOneBit(a ^ c) ^ getLastOneBit(b ^ c),成对元素不影响结果
lastOneBit ^= getLastOneBit(abc ^ haha[i]);
//lastOneBit = getLastOneBit(lastOneBit); // 原博客中有这句,实际不需要 int c = ;
for (int i = ; i < haha.size(); i++) // 第三轮选出所有最低 "1" 位与 lastOneBit 相同的元素(a 和 b 肯定不在里边),用异或找出 c
{
if (getLastOneBit(abc ^ haha[i]) == lastOneBit)
c ^= haha[i];
}
cout << "findOne -> c: " << c << endl;
return c;
}
int main()
{
vector<int> haha = { , , , , , , , , };
int c = findOne(haha); // 先找出一个非重复的元素
haha.push_back(c); // 将找到的元素添加到原数组末尾,相当于去掉了这个非重复元素
vector<int> result = findTwo(haha); // 寻找剩余两个非重复元素
result.push_back(c); cout << "result -> a: " << result[] << ", b: " << result[] << ", c: " << result[] << endl;
return ;
}
■ 输出结果
findOne -> c:
findTwo -> a: , b:
result -> a: , b: , c:
▶ 格雷码与普通二进制码相互转换。证明使用中括号记法,考虑结果每一位来源于 bin 的哪几位的计算
gray = bin ^ (bin >> )
bin = gray ^ (gray >> )
位运算骚操作 Part 3的更多相关文章
- 位运算骚操作 Part 1
▶ 原文标题<Bit Twiddling Hacks>,地址:https://graphics.stanford.edu/~seander/bithacks.html ▶ 额外参考资料:h ...
- 位运算骚操作 Part 2
▶ 计算 unsigned int v 的以 2 为底的对数,结果放入 unsigned int r . // 方法零 #pragma unroll ;v; r++, v >>= ); / ...
- java位运算(操作)的使用
位操作是程序设计中对位模式按位或二进制数的一元和二元操作. 在许多古老的微处理器上, 位运算比加减运算略快, 通常位运算比乘除法运算要快很多. 在现代架构中, 情况并非如此:位运算的运算速度通常与加法 ...
- C 碎片九 预处理&位运算&文件操作
一.预处理 预处理语句:#开头的语句,在预处理阶段处理预处理语句.包括宏定义.文件包含处理.条件编译 1, 宏定义 1. 不带参数宏定义:#define 标识符 字符串 #define PI 3.1 ...
- LeetCode | 289. 生命游戏(原地算法/位运算)
记录dalao的位运算骚操作 根据百度百科 ,生命游戏,简称为生命,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机. 给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细 ...
- 位运算之——按位与(&)操作——(快速取模算法)
学习redis 字典结构,hash找槽位 求槽位的索引值时,用到了 hash值 & sizemask操作, 其后的scan操作涉及扫描顺序逻辑,对同模的槽位 按一定规则扫描! 其中涉及位运算 ...
- 【Java基础】14、位运算之——按位与(&)操作——(快速取模算法)
学习redis 字典结构,hash找槽位 求槽位的索引值时,用到了 hash值 & sizemask操作, 其后的scan操作涉及扫描顺序逻辑,对同模的槽位 按一定规则扫描! 其中涉及位运算 ...
- 全国计算机等级考试二级教程-C语言程序设计_第15章_位运算
位运算,不适用于实数,仅仅适用于整数.字符. C语言的位运算只能操作整数.字符,实数是指数方式表示的,不适用于位运算. #define _CRT_SECURE_NO_WARNINGS #include ...
- LeetCode解题中位运算的运用
位运算是我最近才开始重视的东西,因为在LeetCode上面刷题的时候发现很多题目使用位运算会快很多.位运算的使用包含着许多技巧(详细可以参考http://blog.csdn.net/zmazon/ar ...
随机推荐
- 第四次Scrum冲刺----Life in CCSU
一.第四次Scrum任务 小组GitHub地址链接 个人GitHub地址链接:https://github.com/2505486985/FirstScrum 继续上次完成的任务,这次完成校园服务中的 ...
- classic code review
package dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSe ...
- CNN试验记录
CIFAR-10 图像处理:(预处理还是很重要的) 数据随机裁剪,填充0 依概率p水平翻转 1.VGG16 SGD lr=0.01 momentum 0.9 weight_decay=0.0001 e ...
- Kali安装zmap简单介绍
zmap是一个非常方便的扫描器,跟nmap和masscan一样,不过区别在于zmap他快,号称是一小时扫遍整个互联网.主要使用方式是TCP SYN scan.TCP connectscan.UDP s ...
- magento 1.9 nginx 404
原来的nginx 配置 lnmp 环境默认的 location ~ [^/]\.php(/|$) { fastcgi_param SCRIPT_FILENAME $document_root$fast ...
- servlet中的request和response
request对象 1.什么是请求 a.浏览器向服务器发送数据就是请求. 一.request功能1--获取数据 1.获取浏览器相关的信息 getRequestURL方法 -- 返回客户端发出请求完整U ...
- c# 程序只能运行一次(多次运行只能打开同一个程序)
转自:https://social.msdn.microsoft.com/Forums/zh-CN/6398fb10-ecc2-4c03-ab25-d03544f5fcc9/2291420309357 ...
- 环境搭建--使用pytharm远程调试树莓派
对于Linux和文本编辑器不那么熟悉的小伙伴来说,直接在树莓派中写程序可谓是痛苦万分.本文将介绍如何使用PyCharm远程调试树莓派,并同步当前python文件到树莓派中. 配置环境 首先要在个人电脑 ...
- PySide2兼容PySide1的补丁代码
Maya2017以及Nuke10的PySide都升级到PySide2了,之前PySide1的大量代码都无法在新软件上使用,这主要是由于PySide2不仅调整了模块位置,还增删了一系列模块,下面我分享一 ...
- 为什么HTML中的多个空格或是回车在浏览器上只能显示出一个?
我们在学习HTML的时候可能书本或是老师会告诉我们一件事,就是在HTML中不管我们在两个文本之间加上多少连续的空格或是回车,到了浏览器里面只能显示出一个来.但是我们从来不知道为什么. 原因很简单,因为 ...