i & (i-1) 位运算技巧完全指南
学习日期:2025年11月7日
主题:位运算中最经典的技巧之一
📌 核心原理
基本效果
i & (i-1) 会清除 i 二进制表示中最右边的 1
原理解析
当一个数字减1时,从二进制角度会发生:
情况1:末位是1
7 = 0111
6 = 0110 ← 只有最后一位从1变0
情况2:末位是0(有连续的0)
12 = 1100 ← 最右边的1
11 = 1011 ← 这个1变成0,后面所有0变成1
规律: i-1 会把最右边的1变成0,并把这个1右边的所有0变成1
位运算效果
i = xxxx...x1000 (最右边的1,后面跟着若干个0)
i-1 = xxxx...x0111 (这个1变0,后面的0都变1)
i&(i-1)= xxxx...x0000 (AND运算,只保留左边相同的部分)
结果:去掉了最右边的1!
💡 具体示例
示例1:数字 6
i = 6 = 0110 (二进制)
i-1 = 5 = 0101 (二进制)
i&(i-1)= 4 = 0100 (二进制)
↑ 最右边的1被清除了
示例2:数字 12
i = 12 = 1100 (二进制)
i-1 = 11 = 1011 (二进制)
i&(i-1)= 8 = 1000 (二进制)
↑ 最右边的1被清除了
示例3:数字 7
i = 7 = 0111 (二进制)
i-1 = 6 = 0110 (二进制)
i&(i-1)= 6 = 0110 (二进制)
↑ 最右边的1被清除了
🎯 LeetCode算法题应用
1. 判断2的幂 ⭐⭐⭐
LeetCode 231 - Power of Two
public boolean isPowerOfTwo(int n) {
// 2的幂只有1个1:1, 10, 100, 1000...
// n & (n-1) 会去掉唯一的1,结果为0
return n > 0 && (n & (n - 1)) == 0;
}
// 示例:
// 4 = 0100, 4&3 = 0100&0011 = 0000 ✓
// 8 = 1000, 8&7 = 1000&0111 = 0000 ✓
// 6 = 0110, 6&5 = 0110&0101 = 0100 ✗ (不是0)
原理: 2的幂在二进制中只有1个1,去掉后就是0
2. 统计二进制中1的个数 ⭐⭐⭐
LeetCode 191 - Number of 1 Bits (汉明重量)
public int hammingWeight(int n) {
int count = 0;
while (n != 0) {
n &= (n - 1); // 每次去掉最右边的1
count++;
}
return count;
}
// 示例:
// n = 11 = 1011
// 1011 & 1010 = 1010 (去掉1个1, count=1)
// 1010 & 1001 = 1000 (去掉1个1, count=2)
// 1000 & 0111 = 0000 (去掉1个1, count=3)
3. 前n个数字二进制中1的个数 ⭐⭐⭐
LeetCode 338 - Counting Bits
public int[] countBits(int n) {
int[] result = new int[n + 1];
for (int i = 1; i <= n; i++) {
result[i] = result[i & (i - 1)] + 1;
}
return result;
}
核心思想:
i & (i-1)得到的数字,比i少一个1- 所以
i的1的个数 =i & (i-1)的1的个数 + 1 - 这是动态规划,利用之前计算好的结果
推导过程示例(n=7):
i=0: 0000, result[0] = 0 (基础情况)
i=1: 0001, 1&0=0, result[1] = result[0] + 1 = 1
i=2: 0010, 2&1=0, result[2] = result[0] + 1 = 1
i=3: 0011, 3&2=2, result[3] = result[2] + 1 = 2
i=4: 0100, 4&3=0, result[4] = result[0] + 1 = 1
i=5: 0101, 5&4=4, result[5] = result[4] + 1 = 2
i=6: 0110, 6&5=4, result[6] = result[4] + 1 = 2
i=7: 0111, 7&6=6, result[7] = result[6] + 1 = 3
关键理解:
- i=6 时,6 & 5 = 4
- 6 (0110) 有2个1,4 (0100) 有1个1
- 关系:6的1个数 = 4的1个数 + 1 → 2 = 1 + 1 ✓
4. 判断4的幂 ⭐⭐
LeetCode 342 - Power of Four
public boolean isPowerOfFour(int n) {
// 首先必须是2的幂(只有1个1)
// 其次,这个1必须在奇数位(1, 4, 16, 64...)
// 0x55555555 = 0101 0101 0101 0101 0101 0101 0101 0101
return n > 0 && (n & (n - 1)) == 0 && (n & 0x55555555) != 0;
}
// 4 = 0100 ✓ (第3位,奇数位)
// 16 = 0001 0000 ✓ (第5位,奇数位)
// 8 = 1000 ✗ (第4位,偶数位)
5. 汉明距离 ⭐⭐
LeetCode 461 - Hamming Distance
public int hammingDistance(int x, int y) {
int xor = x ^ y; // 不同的位是1
int count = 0;
while (xor != 0) {
xor &= (xor - 1); // 去掉一个1
count++;
}
return count;
}
6. 二进制手表 ⭐
LeetCode 401 - Binary Watch
public List<String> readBinaryWatch(int turnedOn) {
List<String> result = new ArrayList<>();
for (int h = 0; h < 12; h++) {
for (int m = 0; m < 60; m++) {
// 统计h和m的二进制中1的个数
if (countOnes(h) + countOnes(m) == turnedOn) {
result.add(String.format("%d:%02d", h, m));
}
}
}
return result;
}
private int countOnes(int n) {
int count = 0;
while (n != 0) {
n &= (n - 1);
count++;
}
return count;
}
📱 Android开发实战应用
1. Bitmap处理(极其重要!)⭐⭐⭐⭐⭐
BitmapFactory要求inSampleSize必须是2的幂
public class BitmapHelper {
/**
* 计算合适的inSampleSize
* BitmapFactory要求inSampleSize必须是2的幂
*/
public static int calculateInSampleSize(
BitmapFactory.Options options,
int reqWidth,
int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
// 验证是否是2的幂
if (!isPowerOfTwo(inSampleSize)) {
inSampleSize = nextPowerOfTwo(inSampleSize);
}
return inSampleSize;
}
// 判断是否是2的幂
public static boolean isPowerOfTwo(int n) {
return n > 0 && (n & (n - 1)) == 0;
}
// 找到下一个2的幂
public static int nextPowerOfTwo(int n) {
n--;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
return n + 1;
}
}
// 使用示例
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.large_image, options);
options.inSampleSize = BitmapHelper.calculateInSampleSize(options, 100, 100);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.large_image, options);
2. LruCache缓存优化 ⭐⭐⭐⭐⭐
缓存大小设为2的幂,性能更好
public class CacheHelper {
/**
* LruCache的大小通常设置为2的幂次,性能更好
*/
public static int calculateCacheSize(int maxMemory) {
int cacheSize = maxMemory / 8; // 使用1/8的内存
// 向下舍入到最近的2的幂
return roundDownToPowerOfTwo(cacheSize);
}
// 向下舍入到2的幂
private static int roundDownToPowerOfTwo(int n) {
if (n <= 0) return 1;
if ((n & (n - 1)) == 0) return n; // 已经是2的幂
// 找到最高位的1
int highestBit = 0;
while (n > 0) {
highestBit = n;
n &= (n - 1); // 去掉最低的1
}
return highestBit;
}
}
// 实际使用
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = CacheHelper.calculateCacheSize(maxMemory);
LruCache<String, Bitmap> memoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount() / 1024;
}
};
3. 权限管理 ⭐⭐⭐⭐
public class PermissionChecker {
public static final int READ = 1; // 0001
public static final int WRITE = 2; // 0010
public static final int EXECUTE = 4; // 0100
public static final int DELETE = 8; // 1000
// 检查是否只有一个权限(在某些场景需要单一权限)
public static boolean hasSinglePermission(int permissions) {
// 如果只有一个1,去掉后就是0
return permissions > 0 && (permissions & (permissions - 1)) == 0;
}
// 统计权限数量
public static int countPermissions(int permissions) {
int count = 0;
while (permissions != 0) {
permissions &= (permissions - 1);
count++;
}
return count;
}
}
// 使用示例
int userPerm = READ | WRITE; // 0011, 有2个权限
boolean single = PermissionChecker.hasSinglePermission(userPerm); // false
int count = PermissionChecker.countPermissions(userPerm); // 2
4. SharedPreferences功能标志 ⭐⭐⭐⭐
public class FeatureFlags {
private static final String PREF_NAME = "feature_flags";
public static final int FEATURE_DARK_MODE = 1; // 0001
public static final int FEATURE_NOTIFICATION = 2; // 0010
public static final int FEATURE_AUTO_SYNC = 4; // 0100
public static final int FEATURE_ANALYTICS = 8; // 1000
private SharedPreferences prefs;
public FeatureFlags(Context context) {
prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
}
// 获取启用的功能数量
public int getEnabledFeatureCount() {
int flags = prefs.getInt("enabled_features", 0);
int count = 0;
while (flags != 0) {
flags &= (flags - 1);
count++;
}
return count;
}
// 检查是否只启用了一个功能
public boolean hasSingleFeatureEnabled() {
int flags = prefs.getInt("enabled_features", 0);
return flags > 0 && (flags & (flags - 1)) == 0;
}
}
5. 自定义View状态管理 ⭐⭐⭐⭐
public class CustomButton extends View {
// 状态标志
private static final int STATE_NORMAL = 0;
private static final int STATE_PRESSED = 1 << 0; // 1
private static final int STATE_FOCUSED = 1 << 1; // 2
private static final int STATE_DISABLED = 1 << 2; // 4
private static final int STATE_SELECTED = 1 << 3; // 8
private int currentState = STATE_NORMAL;
// 检查是否只有一个状态激活
public boolean isSingleStateActive() {
return currentState > 0 && (currentState & (currentState - 1)) == 0;
}
// 统计激活的状态数
public int getActiveStateCount() {
int count = 0;
int temp = currentState;
while (temp != 0) {
temp &= (temp - 1);
count++;
}
return count;
}
// 根据状态数量决定绘制逻辑
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isSingleStateActive()) {
// 单一状态,简单绘制
drawSimpleState(canvas);
} else {
// 复合状态,复杂绘制
drawComplexState(canvas);
}
}
}
6. 网络状态检查 ⭐⭐⭐
public class NetworkHelper {
public static final int NETWORK_WIFI = 1; // 0001
public static final int NETWORK_MOBILE = 2; // 0010
public static final int NETWORK_ETHERNET = 4; // 0100
public static final int NETWORK_VPN = 8; // 1000
// 检查是否只连接了一种网络类型
public static boolean isSingleNetworkType(int networkTypes) {
return networkTypes > 0 && (networkTypes & (networkTypes - 1)) == 0;
}
// 统计当前连接的网络类型数量
public static int countNetworkTypes(int networkTypes) {
int count = 0;
while (networkTypes != 0) {
networkTypes &= (networkTypes - 1);
count++;
}
return count;
}
}
🔍 如何发现这类规律?
通用方法论
1. 从暴力解法开始观察
// 先写最简单的代码
for (int i = 0; i <= n; i++) {
int count = 0;
int num = i;
while (num > 0) {
count += num & 1;
num >>= 1;
}
result[i] = count;
}
// 列出具体的值,找规律
i 二进制 1的个数
0 0000 0
1 0001 1
2 0010 1
3 0011 2
4 0100 1
5 0101 2
2. 观察数字之间的关系
- "相邻的数字有什么关系?"
- "偶数和奇数有什么规律?"
- "能否用前面的结果推导后面的?"
3. 尝试常见位运算
常见的位运算就那么几种:
i & (i-1)← 去掉最右边的1i & (-i)← 只保留最右边的1i >> 1← 除以2i & 1← 判断奇偶
4. 从已知技巧类推
// 已知技巧:判断一个数有多少个1
while (num > 0) {
num &= (num - 1); // 每次去掉一个1
count++;
}
// 类推:
// 既然 i & (i-1) 可以去掉一个1,那么:
// i 的1个数 = [i & (i-1)] 的1个数 + 1
5. 问自己问题
- ✅ 相邻数字有什么关系?
- ✅ 能否用前面的结果算后面的?
- ✅ 是否有周期性/对称性?
- ✅ 除以2、乘以2有什么规律?
- ✅ 奇数偶数有什么不同?
📊 应用场景总结
LeetCode算法题
| 题目 | 难度 | 应用 |
|---|---|---|
| LC 231 - Power of Two | 简单 | 判断2的幂 |
| LC 191 - Number of 1 Bits | 简单 | 统计1的个数 |
| LC 338 - Counting Bits | 简单 | 动态规划 |
| LC 342 - Power of Four | 简单 | 判断4的幂 |
| LC 461 - Hamming Distance | 简单 | 汉明距离 |
| LC 401 - Binary Watch | 简单 | 二进制手表 |
Android开发场景
| 场景 | 频率 | 重要性 | 应用 |
|---|---|---|---|
| Bitmap处理 | ⭐⭐⭐⭐⭐ | 极高 | inSampleSize必须是2的幂 |
| LruCache | ⭐⭐⭐⭐⭐ | 极高 | 缓存大小优化 |
| 权限管理 | ⭐⭐⭐⭐ | 高 | 检查单一权限 |
| 标志位管理 | ⭐⭐⭐⭐ | 高 | 功能开关、状态管理 |
| 自定义View | ⭐⭐⭐⭐ | 高 | 状态管理 |
| 网络检查 | ⭐⭐⭐ | 中 | 网络类型统计 |
💡 核心记忆点
1. 基本效果
i & (i-1) → 去掉最右边的1
2. 判断2的幂
(n & (n - 1)) == 0 // 且 n > 0
3. 统计1的个数
while (n != 0) {
n &= (n - 1);
count++;
}
4. 动态规划递推
result[i] = result[i & (i-1)] + 1;
5. 向下舍入到2的幂
while (n > 0) {
highestBit = n;
n &= (n - 1);
}
return highestBit;
🎯 最佳实践
Android开发建议
- 缓存大小都用2的幂 → 性能更好,内存对齐
- 标志位用位运算 → 节省内存,操作快速
- 图片缩放必须2的幂 → Android系统要求
- 能用位运算就用 → 比算术运算快
学习建议
- 大量练习 → 见多识广
- 系统观察 → 有方法地看
- 勇于尝试 → 试错不可怕
- 积累经验 → 记住常见技巧
📝 记忆口诀
i & (i-1) 去右边的1
判断2的幂,统计1个数
动态规划也常用
位运算题见它就不慌
Android开发很实用
Bitmap缓存都要它
标志管理更方便
性能优化少不了
✅ 推荐练习顺序
算法题
- LC 231 - Power of Two (最简单,入门)
- LC 191 - Number of 1 Bits (经典应用)
- LC 338 - Counting Bits (DP结合)
- LC 342 - Power of Four (进阶)
- LC 461 - Hamming Distance (综合应用)
实战项目
- 写一个图片加载工具类,用2的幂优化
- 实现一个权限管理系统
- 自定义View的状态管理
- 功能开关的标志位管理
🔗 相关知识点
- 位运算基础:
&, |, ^, ~, <<, >> - 动态规划
- 二进制数学
- Android性能优化
- 内存管理
最后提醒: i & (i-1) 是位运算中最经典的技巧之一,务必掌握!在面试和实际开发中都会经常用到。
整理完成 - 2025年11月7日