基本概念
一个数的二进制表示末尾0的个数表示该数能够被2连续整除的次数。例如:
- 8 的二进制表示为
1000
,末尾有3个0。 - 12 的二进制表示为
1100
,末尾有2个0。
使用位运算提取末尾0的个数
通过位运算,可以高效地计算一个整数末尾0的个数。具体方法如下:
-
位运算方法:
int ZeroBit(int n) { return log2f(n & -n); }
-
说明:
n & -n
提取了n
的二进制表示中最右边的1及其右边所有的0,这样我们就能得到一个只有最右边1位为1的数。
下面以24为例演示n & -n的过程11000 (n) 01000 (-n) ------ 01000 (n & -n)
log2f
计算这个数的二进制对数,也就是末尾0的个数。
示例代码
下面是完整的示例代码,用于提取一个整数末尾0的个数:
#include <iostream>
#include <cmath>
int ZeroBit(int n) {
return log2f(n & -n);
}
int main() {//k为n的二进制表达式末位'0'的个数
unsigned long k;
int n;
std::cin >> n;
_BitScanForward(&k, n);//在Visual Studio编译器下使用
std::cout << k << '\n';
std::cout << ZeroBit(n) << '\n';
return 0;
}
// 在GCC或G++中,应使用 __builtin_ctz(n)
//具体使用方法如下
// unsigned long k = __builtin_ctz(n);
上述三种方法的时间复杂度都是O(1)
解释
-
输入输出:
- 输入一个整数
n
。 - 使用
_BitScanForward
或__builtin_ctz
提取末尾0的个数并输出。 - 使用
ZeroBit
函数提取末尾0的个数并输出。
- 输入一个整数
-
函数使用:
_BitScanForward(&k, n)
是 MSVC 特有的函数,在编译器为Visual Studio C++的时候使用这个函数。在某些编译器下,可能需要加头文件#include<intrin.h>。在函数的调用过程中,'&'引用符号不可以省略,否则k无法被写入。__builtin_ctz(n)
是 GCC 和 Clang 中的内置函数,在编译器为GCC或G++的时候使用这个函数。直接使用不需要加头文件。ZeroBit(n)
函数为自定义,万用。
实例
输入一个正整数,求这个数的最大的奇因数。先做个因数和最大基因数概念的简易阐述。
因数
因数(或称为约数)是能被一个整数整除的数。例如,12的因数包括:1, 2, 3, 4, 6, 12,因为12能够被这些数整除而没有余数。
- 6的因数是:1, 2, 3, 6
- 15的因数是:1, 3, 5, 15
最大奇因数
最大奇因数是一个整数的所有因数中最大的奇数因数。奇数因数是指不能被2整除的因数。
-
对于12:
- 因数包括:1, 2, 3, 4, 6, 12
- 其中奇数因数是:1, 3
- 最大奇因数是:3
-
对于18:
- 因数包括:1, 2, 3, 6, 9, 18
- 其中奇数因数是:1, 3, 9
- 最大奇因数是:9
下面给出函数部分。
// 函数:求最大奇因数
int Odd(int n) {
return n / (n & -n);
}
解释
-
Odd函数:
n & -n
提取了n
的二进制表示中最右边的1及其右边所有的0,这样我们就能得到一个只有最右边1位为1的数。n / (n & -n)
将原数n
除以这个数,去除了所有的2的幂因子,剩下的就是最大的奇因数。- 时间复杂度O(1)。
当然,利用 _BitScanForward(&k, n)
或__builtin_ctz(n)
也可以实现。时间复杂度也是O(1)。
int Odd(int n) {
unsigned long k;
_BitScanForward(&k, n);
return n >> k;
}
int Odd(int n) {
int k = __builtin_ctz(n);
return n >> k;
}
谢谢观看