LeetCode 1009 & 476 数字的补数

LeetCode 1009 & 476 数字的补数

问题描述

给定一个正整数(或非负整数),输出它的补数 。补数是对该数的二进制表示(不包含前导零 )按位取反的结果。

例如:5 的二进制是 101,取反得 010,即十进制 2。

LeetCode 1009 题名为"十进制整数的补码",LeetCode 476 题名为"数字的补数",两者本质完全相同。


解法一:逐位取反法(对应 LeetCode 1009 代码)

cpp 复制代码
class Solution {
public:
    int bitwiseComplement(int n) {
        if(!n) return 1;                // 特殊情况:0 的二进制取反应为 1
        int res = 0;
        for(int i = 0; 1 << i <= n; i++) // 遍历有效位,直到 2^i 超过 n
            res |= !(n >> i & 1) << i;   // 取出第 i 位,取反后左移 i 位,累加到结果
        return res;
    }
};

思路解析

  • 核心思想:逐位处理原数的二进制有效位,对每一位取反后构建新数。
  • 步骤
    1. n == 0,直接返回 1(二进制 0 取反为 1)。
    2. 从低位到高位循环,i 表示当前位,循环条件 1 << i <= n 保证只处理有效位(忽略前导零)。
    3. (n >> i) & 1 取出第 i 位的值(0 或 1)。
    4. 逻辑非 ! 将位值取反:!0 = 1!1 = 0
    5. 将取反后的值左移 i 位,放回原来的位权位置,通过按位或 |= 累加到结果 res
    6. 循环结束,res 即为补数。

复杂度分析

  • 时间复杂度:O(log n),循环次数等于 n 的二进制位数。
  • 空间复杂度:O(1),只使用了常数变量。

解法二:掩码取反法(对应 LeetCode 476 代码)

cpp 复制代码
class Solution {
public:
    int findComplement(int num) {
        if(!num) return 1;               // 特殊情况处理
        int cnt = 0, x = num;
        while(x) {                        // 统计有效位数
            x >>= 1;
            cnt++;
        }
        // 构造低 cnt 位全 1 的掩码,与 ~num 按位与,保留低位
        return ~num & ((1ll << cnt) - 1);
    }
};

思路解析

  • 核心思想:先确定原数的有效位数,构造一个掩码,将取反后的结果截断到有效位。
  • 步骤
    1. num == 0,直接返回 1
    2. 统计有效位数 cnt:将 num 不断右移直到 0,每移一次计数加 1。
    3. 构造掩码 mask = (1ll << cnt) - 1,即低 cnt 位全为 1 的二进制数(使用 1ll 防止左移溢出 int 范围)。
    4. num 按位取反得 ~num,此时高位(超出有效位)也被取反,与掩码按位与 & 后,高位清零,只保留低 cnt 位,即为正确补数。

复杂度分析

  • 时间复杂度:O(log n),统计位数需要循环,但取反和与运算是 O(1)。
  • 空间复杂度:O(1)。

两种解法对比

对比维度 解法一(逐位取反) 解法二(掩码取反)
核心思想 逐位遍历原数,取反后构建新数 先求位数,构造掩码,再取反截断
时间复杂度 O(log n)(循环位数次) O(log n)(统计位数),位运算 O(1)
空间复杂度 O(1) O(1)
代码简洁性 较直观,适合初学者 简洁,利用位运算技巧
边界处理 均需单独处理 n=0 的情况 均需单独处理 n=0 的情况
潜在问题 掩码左移需用 1ll 避免溢出(int 最大 31 位时左移 31 位可能溢出)

共同点

  • 都正确处理了不包含前导零的二进制取反。
  • 都对 n == 0 做了特殊处理(返回 1)。
  • 均基于位运算实现,未借助字符串转换。

不同点

  • 实现方式:解法一在循环中直接计算结果,解法二先统计位数再使用取反和掩码。
  • 取反手段 :解法一用逻辑非 ! 配合移位实现位取反;解法二直接用按位取反 ~,再通过与掩码结合。
  • 溢出考虑 :解法二必须注意掩码构造时的溢出问题,使用 1ll 转为 long long 是必要的。

总结

两道题本质相同,解法各有特色。

  • 解法一(逐位取反)思路直观,适合初学者理解二进制位操作。
  • 解法二(掩码取反)代码更简洁,利用掩码一次完成截断,体现了位运算的灵活性,但在使用时需留意数据范围。

掌握这两种解法有助于加深对位运算和二进制表示的理解,在实际开发中可根据场景选择合适的方法。

相关推荐
weixin_307779133 分钟前
基于Vosk与CTranslate2的实时语音识别翻译系统 —— 完整C++实现详解
人工智能·算法·自动化·语音识别·原型模式
akarinnnn6 分钟前
深入理解内存函数:原理、应用与优化
c语言·网络·数据结构·算法
一行代码一行诗++14 分钟前
for循环中的break和continue
数据结构·算法
Tisfy18 分钟前
LeetCode 3043.最长公共前缀的长度:哈希表(不转string)
算法·leetcode·散列表·题解·哈希表
代码中介商18 分钟前
排序算法完全指南(三):插入排序深度详解
算法·排序算法
承渊政道22 分钟前
【贪心算法】(经典实战应用解析(六):整数替换、俄罗斯套娃信封问题、可被三整除的最⼤和、距离相等的条形码、重构字符串)
c++·算法·leetcode·贪心算法·排序算法·动态规划·哈希算法
WL_Aurora25 分钟前
Python 算法基础篇之排序算法(二):希尔、快速、归并
python·算法·排序算法
闻缺陷则喜何志丹31 分钟前
【图论 树 启发式合并】P7165 [COCI2020-2021#1] Papričice|普及+
c++·算法·启发式算法·图论··洛谷
alexwang21133 分钟前
AT_abc458_d [ABC458D] Chalkboard Median题解
c++·算法·题解·atcoder
故事和你9133 分钟前
洛谷-【图论2-4】连通性问题1
开发语言·数据结构·c++·算法·动态规划·图论