Swift - LeetCode - 数字的补数

72 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第15天,点击查看活动详情

题目

对整数的二进制表示取反(0110)后,再转换为十进制表示,可以得到这个整数的补数。

例如,整数 5 的二进制表示是 "101",取反后得到 "010",再转回十进制表示得到补数 2。 给你一个整数 num,输出它的补数。

示例 1:

  • 输入: num = 5
  • 输出: 2
  • 解释: 5 的二进制表示为 101(没有前导零位),其补数为 010。所以你需要输出 2。

示例 2:

  • 输入: num = 1
  • 输出: 0
  • 解释: 1 的二进制表示为 1(没有前导零位),其补数为 0。所以你需要输出 0。

方法一:位运算

思路及解法

根据题目的要求,我们需要将 num\textit{num} 二进制表示的每一位取反。然而在计算机存储整数时,并不会仅仅存储有效的二进制位。例如当 num=5\textit{num} = 5 时,它的二进制表示为 (101)2(101)_2,而使用 3232 位整数存储时的结果为:

(0000 0000 0000 0000 0000 0000 0000 0101)2(0000~0000~0000~0000~0000~0000~0000~0101)_2

因此我们需要首先找到 num\textit{num} 二进制表示最高位的那个 1,再将这个 1 以及更低的位进行取反。

如果 num\textit{num} 二进制表示最高位的 1 是第 i (0i30)i~(0 \leq i \leq 30) 位,那么一定有:

2inum<2i+12^i \leq \textit{num} < 2^{i+1}

因此我们可以使用一次遍历,在 [0,30][0, 30] 中找出 ii 的值。

在这之后,我们就可以遍历 num\textit{num} 的第 0i0 \sim i 个二进制位,将它们依次进行取反。我们也可以用更高效的方式,构造掩码 mask=2i+11\textit{mask} = 2^{i+1} - 1,它是一个 i+1i+1 位的二进制数,并且每一位都是 11。我们将 num\textit{num}mask\textit{mask} 进行异或运算,即可得到答案。

细节

i=30i=30 时,构造 mask=2i+11\textit{mask} = 2^{i+1} - 1 的过程中需要保证不会产生整数溢出。下面部分语言的代码中对该情况进行了特殊判断。

代码

class Solution {
    func findComplement(_ num: Int) -> Int {
        var highbit: Int = 0
        for i in 1...30 {
            if num >= 1 << i {
                highbit = i
            }
            else {
                break
            }
        }
        let mask: Int = highbit == 30 ? 0x7fffffff : (1 << (highbit + 1)) - 1
        return num ^ mask
    }
}

复杂度分析

  • 时间复杂度:O(lognum)O(lognum)。找出 num\textit{num} 二进制表示最高位的 11 需要的时间为 O(lognum)O(\log \textit{num})

  • 空间复杂度:O(1)O(1)