位运算与二进制表示集合
位运算与二进制表示集合
位运算
运算符
运算 | 运算符 | 数学符号表示 | 解释 |
---|---|---|---|
与 | 只有两个对应位都为 |
||
或 | 只要两个对应位有一个 |
||
异或 | 只有两个对应位不同时才为 |
||
取反 | 无 | 二进制位均 全部取反( |
|
左移 | 无 | ||
带符号右移 | 无 | 正数右移后,高位补 |
|
无符号右移( |
无 | 无论正负,高位均补 |
原码、反码、补码
最高位是符号位,以下以
编码方式 | 解释 | 举例 |
---|---|---|
原码 | 原码就是符号位加上真值的绝对值,即用第一位表示符号,其余位表示值 | |
反码 | 正数的反码是其本身。负数的反码是在其原码的基础上,符号位不变,其余各个位取反. | |
补码 | 正数的补码就是其本身。负数的补码是在其原码的基础上,符号位不变,其余各位取反,最后+1。(即在反码的基础上+1) |
计算机采用补码的编码方式
应用
一个数乘除 的非负整数次幂
计算
计算
操作一个数的二进制位
操作 | 实现 | 举例 |
---|---|---|
获取 |
||
将 |
||
将 |
||
将 |
||
获取 |
||
将 |
||
将 |
||
将 |
对于 向前进位、原地加、向前借位、原地减
这四种情况,现场挑几个数字(如
一些自带的二进制方法
大多通过二分查找的方式实现
C++(头文件stdlib.h 中) |
Java(均可通过使用Long 类代替Integer 类) |
|
---|---|---|
十进制转换其他进制( |
itoa(数字, char[] ans, 进制) ,ltoa(数字, char[] ans, 进制) 同时返回值均为 char[] 类型的答案 |
Integer.toString(数字, 进制) |
任意进制转换十进制( |
strtol(原进制字符数组, 接受剩余非法字符, 进制) 返回十进制数strtol 返回long 类型,strtoll 返回long long 类型strtoul 返回unsigned long 类型。还有double 等类型函数 |
Integer.parseInt(字符串String, 进制) |
二进制中 |
int __builtin_popcount(unsigned int x) int __builtin_popcountll(unsigned long long x) 等等 |
Integer.bitCount(数字) |
二进制末尾连续 |
int __builtin_ctz(unsigned int x) ,long 等其他类型同上 当 |
Integer.numberOfTrailingZeros(数字) 当 |
二进制前导零的个数(可用来求最高位的 |
int __builtin_clz(unsigned int x) long 等其他类型同上当 |
Integer.numberOfLeadingZeros(数字) 当 |
获取符号 | 暂未了解 | Integer.signum(数字) 当 数字大于 当 数字等于 当 数字小于 |
获取二进制最后一个 |
暂未了解 | Integer.lowestOneBit(数字) 通过 x & -x 实现 |
获取二进制第一个 |
暂未了解 | Integer.highestOneBit(数字) 通过调用numberOfLeadingZeros(数字) 实现 |
二进制循环左移(低位缺少的位通过高位消去的位补充) | 暂未了解 | Integer.rotateLeft(数字, 移动位数) |
二进制循环右移(高位缺少的位通过低位消去的位补充) | 暂未了解 | Integer.rotateRight(数字, 移动位数) |
二进制按位反转 | 暂未了解 | Integer.reverse(数字) 返回二进制按位反转后的十进制值 |
更多位数
Int
类型的二进制位只有 Long
类型的二进制位也只有
下述表格待补全...
c++bitset (头文件bitset 中) |
JavaBitSet |
|
---|---|---|
例题
class Solution {
public boolean isPowerOfTwo(int n) {
if (n <= 0) return false;
return (n & (n - 1)) == 0;
}
}
class Solution {
public boolean isPowerOfFour(int n) {
if (n <= 0) return false;
if ((n & (n - 1)) != 0) return false;
return (0b10101010101010101010101010101010 & n) == 0;
}
}
class Solution {
public int hammingDistance(int x, int y) {
return Integer.bitCount(x ^ y);
}
}
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
return Integer.bitCount(n);
}
}
public class Solution {
// you need treat n as an unsigned value
public int reverseBits(int n) {
// 或者直接调用
//return Integer.reverse(n);
int ans = 0;
for (int i = 0; i < 16; ++i) {
ans |= (n >> i & 1) << (31 - i);
ans |= (n >> (31 - i) & 1) << i;
}
return ans;
}
}
每四位二进制数对应一位十六进制数
class Solution {
char[] ch = "0123456789abcdef".toCharArray();
final int mask = 0b1111;
public String toHex(int num) {
if (num == 0) return "0";
StringBuilder ans = new StringBuilder();
while (num != 0) {
ans.append(ch[num & mask]);
num >>>= 4; // 注意要无符号右移
}
return ans.reverse().toString();
}
}
数列横着看和竖着看是两种方式,有时另一种会十分简便
class Solution {
public int totalHammingDistance(int[] nums) {
int n = nums.length, ans = 0;
for (int i = 0; i < 32; ++i) {
int sum = 0;
// 计算第i位1的个数
for (int v : nums) {
sum += v >> i & 1;
}
ans += sum * (n - sum);
}
return ans;
}
}
class Solution {
public boolean hasAlternatingBits(int n) {
n ^= n >> 1;
return n != 0 && (n & (n + 1)) == 0;
}
}
class Solution {
// 预处理小时位和分钟位的二进制1的个数
static LinkedList<String>[] hours = new LinkedList[4];
static LinkedList<String>[] minutes = new LinkedList[9];
static {
for (int i = 0; i < 4; ++i) hours[i] = new LinkedList<String>();
for (int i = 0; i < 9; ++i) minutes[i] = new LinkedList<String>();
for (int i = 0; i < 12; ++i) {
hours[Integer.bitCount(i)].add(Integer.toString(i));
}
for (int i = 0; i < 10; ++i) {
minutes[Integer.bitCount(i)].add(":0" + i);
}
for (int i = 10; i < 60; ++i) {
minutes[Integer.bitCount(i)].add(":" + i);
}
}
public List<String> readBinaryWatch(int turnedOn) {
List<String> ans = new LinkedList<String>();
if (turnedOn >= 9) return ans;
// 枚举小时位灯的个数
for (int i = Math.min(3, turnedOn); i >= 0; --i) {
for (String hour : hours[i]) {
for (String minute : minutes[turnedOn - i]) {
ans.add(hour + minute);
}
}
}
return ans;
}
}
二进制表示集合
集合操作
操作 | 集合表示 | 位运算符 |
---|---|---|
交集 | ||
并集 | ||
补集 | ||
差集 | ||
对称差 |
遍历子集
若遍历的是二进制表示除前导 111111
),则可以通过下述方式遍历
int n = 1;
int S = (1 << n) - 1;
for (int i = 1; i <= S; ++i) {
for (int j = 0; j < n; ++j) {//遍历二进制每一位
if ((i >> j & 1) == 1) {//判断第j位是否存在
// do something;
}
}
}
但如果要屏蔽某一位置的遍历(如111110011
),若仍选择通过上述方式遍历,就需要一些判断,更推荐如下做法(逆序遍历)
/*
// 这种写法不会遍历空集
int n = 1;
int S = (1 << n) - 1;
for (int i = S; i != 0; i = (i - 1) & S) {
for (int j = 0; j < n; ++j) { // 遍历二进制每一位
if ((i >> j & 1) == 1) { // 判断第j位是否存在
//do something;
}
}
}
*/
int n = 1;
int S = (1 << n) - 1;
int i = S;
do {
for (int j = 0; j < n; ++j) { // 遍历二进制每一位
if ((i >> j & 1) == 1) { // 判断第j位是否存在
//do something;
}
}
i = (i - 1) & S;
} while (i != S);
原理:
- 减
是为了遍历所有比 小的数,减 的实质就是去掉二进制数的最后一个 ,并在其后面的位上补上 ,如 - & 操作是让原来
二进制上是 的位均保持 - 当
变为空集 时,继续减一会变成 ,而 ,他与 做 & 运算就会重新变为 ,此时循环终止
例题
class Solution {
public List<String> letterCasePermutation(String s) {
int len = s.length();
char[] t = s.toCharArray();
List<String> ans = new LinkedList<>();
ans.add(s);
int S = 0;
for (int i = 0; i < len; ++i) {
if (Character.isDigit(t[i])) continue;
S |= 1 << i;
}
for (int i = S; i != 0; i = (i - 1) & S) {
t = s.toCharArray();
for (int j = 0; j < len; ++j) {
if ((i >> j & 1) == 1) t[j] ^= 32; // 英文字母异或32代表大小写转换
}
ans.add(new String(t));
}
return ans;
}
}
class Solution {
public List<List<Integer>> subsets(int[] nums) {
int len = nums.length;
List<List<Integer>> ans = new ArrayList<>(1 << len);
for (int i = 0, S = 1 << len; i < S; ++i) {
List<Integer> t = new LinkedList<>();
for (int j = 0; j < len; ++j) {
if ((i >> j & 1) == 1) {
t.add(nums[j]);
}
}
ans.add(t);
}
return ans;
}
}
class Solution {
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
int len = nums.length;
List<List<Integer>> ans = new ArrayList<>(1 << len);
for (int i = 0, S = 1 << len; i < S; ++i) {
List<Integer> t = new LinkedList<>();
boolean mark = true;
for (int j = 0; j < len; ++j) {
if ((i >> j & 1) == 1) {
if (j > 0 && nums[j] == nums[j - 1] && (i >> (j - 1) & 1) == 0) {
mark = false;
break;
}
t.add(nums[j]);
}
}
if (mark) ans.add(t);
}
return ans;
}
}
class Solution {
public List<Integer> findNumOfValidWords(String[] words, String[] puzzles) {
HashMap<Integer, Integer> map = new HashMap<>();
for (String s : words) {
// 二进制映射
int mask = 0;
for (int i = 0; i < s.length(); ++i) {
mask |= 1 << (s.charAt(i) - 'a');
}
// 题目保证puzzle字符串长度为 7
// 只加入个数小于等于 7 的减少空间消耗
if (Integer.bitCount(mask) <= 7) {
map.put(mask, map.getOrDefault(mask, 0) + 1);
}
}
List<Integer> ans = new ArrayList<>(puzzles.length);
for (String s : puzzles) {
// 二进制映射
int mask = 0;
// 跳过首字母,之后处理集合的时候单独加上,保证首字母存在
for (int i = 1; i < s.length(); ++i) {
mask |= 1 << (s.charAt(i) - 'a');
}
int cnt = 0;
int begin = s.charAt(0) - 'a';
for (int i = mask; i != 0; i = (i - 1) & mask) {
// 保证首字母存在
cnt += map.getOrDefault(i | (1 << begin), 0);
}
// 处理空集(只有首字母的情况)
cnt += map.getOrDefault(1 << begin, 0);
ans.add(cnt);
}
return ans;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)