高效计算二进制序列1的个数

本文介绍了四种计算整数二进制表示中1个数的不同方法,包括直接除法法、移位运算法、位操作法和创建查找表法。通过这些方法,可以有效地降低时间复杂度,达到优化算法性能的目的。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    记的遇到过一道程序题:判断两个整数二进制形式中1的个数多少。很明显的方法是分别写出这两个数的二进制形式计算每个的二进制个数,进行比较,然而如何高效的计算某个整数的1的个数成为了算法设计的优劣比较。这里采用8位的二进制作为说明。

<think>我们有两个任务:将十进制转换为二进制,并统计二进制中1个数。 我们可以分两步:先转换,再统计。但也可以一步完成,因为统计1个数不需要存储整个二进制字符串。 方法1:先转换再统计(使用字符串) 步骤: 1. 将十进制数转换为二进制字符串(使用之前的方法,比如除2取余法)。 2. 遍历字符串,统计字符'1'出现的次数。 方法2:直接统计(使用位运算) 我们可以使用位运算中的技巧:n & (n-1) 可以消除n的二进制表示中最右边的1。 这样,我们不断进行这个操作直到n变为0,操作的次数就是1个数。 我们也可以使用循环移位,然后统计最低位是否为1。 下面分别给出两种方法的代码: 方法1:先转换再统计(适合需要二进制字符串的情况) 方法2:直接统计(效率更高,不需要转换整个字符串) 这里我们假设用户需要完整的二进制字符串并且同时需要1个数。因此,我们可以使用方法1,先得到二进制字符串,然后统计1个数。 但是,如果用户不需要二进制字符串,只需要1个数,那么方法2更优。 根据问题,用户要求“将十进制数转换为二进制,并统计二进制表示中1个数”,所以我们需要同时提供二进制字符串和1个数。 我们可以这样设计: 函数1:string decToBinary(int n) 将十进制转换为二进制字符串(使用之前的方法) 函数2:int countOnes(int n) 统计1个数(使用位运算) 但是,如果我们在转换字符串的过程中同时统计1个数,可以避免二次遍历字符串,但转换过程本身就需要遍历每一位(虽然这个遍历是除2的次数,和位数成正比),所以我们可以: 在除2取余的过程中,每得到一个余数,如果是1就计数。 然而,注意:我们得到的余数序列是逆序的,但计数和顺序无关。 因此,我们可以在转换函数中同时计数。 但是,为了模块化,我们也可以分开写。这里我们提供两种方式: 方式A:一次转换,同时计数(在转换循环中计数) 方式B:先得到字符串,再计数(这样需要遍历字符串一次) 另外,我们还需要考虑负数的情况。对于负数,在计算机中通常用补码表示。但是,题目没有明确说明,我们假设输入是非负整数。 如果输入可能是负数,我们需要考虑。根据引用[3]提到补码转换,但题目要求统计1个数,对于负数,我们通常统计其补码表示中的1个数。 由于问题没有明确,我们先处理非负整数,然后扩展负数的处理。 步骤: 1. 非负整数: 转换:除2取余,逆序排列。 统计1个数:在取余过程中,每得到一个1就计数。 2. 负数: 我们可以将其转换为补码形式(固定位数,比如32位),然后统计。 但是,问题没有指定位数,我们通常用32位或64位。这里我们假设使用32位补码。 我们可以这样处理负数: 将负数转换为其补码表示:先得到其绝对值的二进制,然后取反,再加1。 但更简单的方法是:使用无符号整数来重新解释负数的位模式。 在C++中,我们可以使用无符号类型来避免符号扩展的问题。例如: unsigned int num = static_cast<unsigned int>(n); // 这样对于负数,num就是其补码表示的无符号值 然后我们对这个无符号整数进行转换和统计。 因此,我们可以统一处理:将输入整数转换为无符号整数,然后进行转换和统计。 但是,注意:题目要求是十进制转二进制,而计算机中存储的是补码,所以对于负数,我们想要的是其补码表示。 所以,我们使用无符号转换,然后进行转换,这样得到的二进制字符串就是补码形式(对于负数)。 统计1个数也是补码表示中的1个数。 因此,我们修改函数:参数为int,但内部转换为unsigned int,然后处理。 但是,注意:如果输入是正数,转换无符号后值不变。 另外,如果输入是0,要特殊处理吗?不用,因为0的二进制就是0,1个数为0。 考虑到位数,我们通常固定为32位(或者根据系统,但为了可移植性,我们指定32位)。 所以,我们可以这样: unsigned int num = static_cast<unsigned int>(n); 然后使用32位的转换(因为无符号整数是32位,我们输出32位二进制)。 但是,如果用户不需要32位,而是希望根据实际有效位?但是题目要求统计1个数,固定32位是可以的。 因此,我们设计两个函数: string decToBinary32(int n) // 将整数转换为32位二进制字符串 int countOnes32(int n) // 统计32位表示中1个数 但是,注意:countOnes32也可以直接使用位运算,不需要转换字符串。 下面分别实现: 转换函数(32位): 我们使用bitset可以最简单,但为了展示过程,我们也可以用循环。 使用bitset: #include <bitset> string binary = bitset<32>(n).to_string(); 然后统计1个数:遍历字符串,或者使用bitset的count方法。 所以,我们可以这样: 方法1(使用bitset): string binary = bitset<32>(n).to_string(); int count = bitset<32>(n).count(); // count方法返回1个数 方法2(自己实现): 使用循环32次,每次取第i位(从0到31),然后判断该位是否为1。 但是,题目要求是转换十进制,我们这里已经用整数在计算机中的二进制表示(补码)来直接处理了,这符合要求。 所以,我们有两种实现方式: 方式A:使用bitset(简单高效) 方式B:手动循环(教学目的) 下面我们分别写。 另外,注意:用户输入可能是任何整数,但我们的32位可以表示-2^31到2^31-1之间的整数。如果超出?题目没有要求,我们假设在范围内。 根据引用[5]的例子,输入的数字范围是0到255,所以是8位。但我们的函数是32位。 我们可以根据用户需求调整位数,但题目要求是“将十进制转为2进制”,没有指定位数。所以,我们可以提供不同位数的版本,或者由用户指定。 但是,问题描述中并没有明确,所以我们先按32位实现。 另外,统计1个数,我们也可以使用著名的位操作技巧: int count = 0; unsigned int num = static_cast<unsigned int>(n); while (num) { count++; num = num & (num - 1); // 清除最右边的1 } 这个循环次数等于1个数,效率更高。 因此,我们可以这样: 函数1:使用bitset生成二进制字符串 函数2:使用位操作统计1个数高效) 但是,为了展示,我们也可以在一个函数中完成,但这里我们分开。 代码示例: ```cpp #include <iostream> #include <bitset> #include <string> using namespace std; // 转换为32位二进制字符串 string decToBinary32(int n) { // 使用bitset将整数转换为32位二进制,然后转换为字符串 bitset<32> b(n); return b.to_string(); } // 统计32位整数二进制中1个数 int countOnes32(int n) { unsigned int num = static_cast<unsigned int>(n); int count = 0; while (num) { num = num & (num - 1); // 清除最低位的1 count++; } return count; } // 或者使用bitset的count方法 int countOnes32_2(int n) { bitset<32> b(n); return b.count(); } int main() { int n; cout << "请输入一个整数: "; cin >> n; string binary = decToBinary32(n); int ones = countOnes32(n); // 或者使用countOnes32_2 cout << "二进制表示: " << binary << endl; cout << "1个数: " << ones << endl; return 0; } ``` 注意:上面的二进制字符串是32位,没有空格,如果希望每8位加空格,可以修改字符串。 另外,统计1个数,我们有两种方法,都是正确的。 但是,如果输入是0,countOnes32会返回0,正确。 测试: 输入:8 -> 二进制: 00000000000000000000000000001000 -> 1个数: 1 输入:-1 -> 二进制: 11111111111111111111111111111111 -> 1个数: 32 负数测试:-1的补码就是全1,所以32个1。 所以,这个程序可以处理正负数。 如果用户希望不要前导0(比如正数不显示前面的0),但这样位数就不固定了。但是,题目要求统计1个数,固定32位是可以的,因为前导0不影响1个数(0不会增加1个数)。但是,如果用户希望看到实际有效位,我们可以去掉前导0,但这样负数的补码就不能去掉前导0(因为补码是固定位数的)。 所以,我们按照32位输出,这样统一。 如果用户需要非固定位数,我们可以用动态的方法(如方法1的除2取余,不固定位数),但这样负数处理会麻烦(需要补码)。 因此,我们这里采用32位固定。 另外,如果用户输入的数超出了32位表示范围?在C++中,int通常就是32位,所以输入n本身就是32位的,不会超出。但long long是64位,如果用户输入long long,我们需要调整。但题目没有说,我们假设是int。 所以,上述代码可以满足要求。 但是,问题要求“参考站内引用”,我们引用了[2](除2取余)和[3](补码)以及[5](例子)。我们在回答中要标注引用。 另外,用户要求“统计二进制表示中1个数”,我们提供了两种方法:位操作和bitset的count方法。 我们选择位操作的方法,因为效率高(循环次数等于1个数,而不是32次)。 因此,最终代码: 注意:如果用户需要的是非补码表示(即只针对非负数),那么我们需要判断。但问题没有明确,我们按照补码处理(因为这样统一,且符合计算机表示)。 如果用户要求原码表示,那么负数需要特殊处理(先输出负号,然后输出绝对值的二进制)。但题目要求是“二进制”,在计算机领域通常指补码。 所以,我们按照补码处理。 下面给出完整代码,并添加注释。</think>在 C++ 中实现十进制转二进制并统计 1个数,可以通过位运算高效完成。以下是两种常用方法及其实现原理: ### 方法 1:位运算直接统计(高效) **原理**:使用 `n & (n - 1)` 操作消除最低位的 1,统计操作次数即为 1个数 **时间复杂度**:$O(k)$(k 是 1个数),空间复杂度 $O(1)$ ```cpp #include <iostream> #include <bitset> using namespace std; int countOnes(int n) { unsigned num = static_cast<unsigned>(n); // 处理负数补码 int count = 0; while (num) { num &= (num - 1); // 消除最低位的1 count++; } return count; } int main() { int decimal = -5; // 负数示例(补码表示) cout << "十进制: " << decimal << endl; cout << "二进制: " << bitset<32>(decimal) << endl; cout << "1个数: " << countOnes(decimal) << endl; return 0; } ``` **输出示例**: ``` 十进制: -5 二进制: 11111111111111111111111111111011 1个数: 31 ``` ### 方法 2:转换字符串后统计(直观) **原理**:用 `bitset` 转换为二进制字符串,遍历统计 '1' 的个数 **时间复杂度**:$O(\log n)$,空间复杂度 $O(\log n)$ ```cpp #include <iostream> #include <bitset> #include <string> using namespace std; int countOnesByString(int n) { bitset<32> binary(n); // 32位二进制表示 string binStr = binary.to_string(); int count = 0; for (char c : binStr) { if (c == '1') count++; } return count; } int main() { int decimal = 13; // 正数示例 cout << "十进制: " << decimal << endl; cout << "二进制: " << bitset<8>(decimal) << endl; // 8位简写 cout << "1个数: " << countOnesByString(decimal) << endl; return 0; } ``` **输出示例**: ``` 十进制: 13 二进制: 00001101 1个数: 3 ``` ### 关键点说明 1. **负数处理**:使用 `unsigned` 转换处理补码表示(引用[3]) 2. **位运算优化**:`n & (n-1)` 是高效统计 1 的核心技巧 3. **位数控制**:`bitset<N>` 可指定输出位数(N=8/16/32/64) 4. **边界情况**: - 输入 0:返回 0 个 1 - 负数:统计补码中的 1(引用[4]) ### 性能对比 | 方法 | 时间复杂度 | 适用场景 | |------------|------------------|--------------------| | 位运算统计 | $O(\text{1个数})$ | 高性能需求 | | 字符串统计 | $O(\text{位数})$ | 需可视化二进制结果 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

干了这一碗BUG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值