食用指南:
对该算法程序编写以及踩坑点很熟悉的同学可以直接跳转到代码模板查看完整代码
只有基础算法的题目会有关于该算法的原理,实现步骤,代码注意点,代码模板,代码误区的讲解
非基础算法的题目侧重题目分析,代码实现,以及必要的代码理解误区
题目描述:
-
给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 1 的个数。
输入格式
第一行包含整数 n。
第二行包含 n 个整数,表示整个数列。输出格式
共一行,包含 n 个整数,其中的第 i 个数表示数列中的第 i 个数的二进制表示中 1 的个数。数据范围
1≤n≤100000,
0≤数列中元素的值≤109
输入样例:
5
1 2 3 4 5
输出样例:
1 1 2 1 2 -
题目来源:https://www.acwing.com/problem/content/803/
题目分析:
-
n在1到十万范围内,int / long long 即可存储
且都是正数,不用考虑补码问题 -
考察经典位运算,结合原码反码补码知识
提醒我更新不动的时候可以发发C语言
算法原理:
模板算法:
- 传送门:暂无C语言数据存储教程
位运算:
-
正整数存储示意图:
以4Byte的int为例:
首位符号位0正1负,余31位从左到右权值230 - 20int a = 1005的存储:
-
>> << 操作符:
对于所有整型,<<左移操作符补码右补0,等同于/2
对于负数,>>右移操作符补码左补1;
对于正数,>>右移操作符补码左补0;
都等同于*2 -
或与反:
或:| , 有 1 则 1
与:& , 有 0 则 0
取反:~ , 0 1 互换
x & -x:
- 规律1:
-x = ~x + 1;
~x就是x所有位取反,+1则又给反码+1
不考虑符号位,-x的源码就是x的补码 - 规律2:
-x & x 得到的是x中最靠右的1的权值 - 规律3:
x -= -x & x
先得到最靠右的1的权值,再把他抹去
当x == 0时,统计得出了x中所有1的个数
前提是没考虑符号位 - 三个规律记住就好,应用不是很多
目前我只在这题和树状数组构造中用到过
代码实现:
- 法一:移位运算符号
#include <iostream>
using namespace std;
int main(){
int n = 0;
cin >>n;
for(int i=0; i<n; i++){
int x = 0;
cin >>x;
int count = 0;
while(x){
if(x & 1) count++;
x=x>>1;
}
cout<< count<< " ";
}
return 0;
}
- 法二:x & -x
#include <iostream>
using namespace std;
int main(){
int n = 0;
cin >>n;
for(int i=0; i<n; i++){
int x = 0;
cin >>x;
int count = 0;
while(x){
x -= -x & x;
count++;
}
cout<< count<< " ";
}
return 0;
}
常见位运算总结:
- 下图摘自 - 极客时间《算法训练营》
- 指定位变化:
- 异或操作:
常见用异或交换两数:
a = a^b; b = a^b; a = a^b;
代码误区:
1. 负数x的x&-x规律:
-
x & -x 肯定是得到的最右的1的权值
毕竟x是正数的时候已经算过-x & x了 -
负数x想要统计补码有多少1:
x是负数,x & -x是正数,
x+=x&-x在将负数向正数靠拢
当最终x == 0时,统计出来的是负数x原码数值域中1的个数
所以怕记混了,就不管正数负数都记成统计原码数值域中1的个数 -
例证: -5 -> -4 ->0
本篇感想:
- 难点在x&-x 和 拓展位运算
- 看完本篇博客,恭喜已登 《练气境-中期》
距离登仙境不远了,加油