通过操作位将数字加 1
位操作使用位运算符(例如 AND(&)、OR(|)、NOT(~)、XOR(^)、左移(<<) 和右移(>>))对位流进行逻辑运算,以获得所需结果。使用位运算符的优势在于我们可以操作单个位,而且它们比其他运算符更快。
问题描述
给定一个数字。仅使用位运算符将该数字加 1 或加 1。 (请勿使用诸如"+"、"-"、"*"或"/"之类的算术运算符)
方法 1:使用二进制补码/非运算符
按位补码/二进制补码使用 NOT(~) 运算符实现。
对于数字 n,n 的按位补码,即 ~n = -(n+1)。因此,我们只需取 ~n 的值并忽略符号即可得到 n+1。这可以使用 abs() 函数来实现,该函数返回变量的值并忽略符号。
伪代码
procedure inc (num) num = |~n| // | | represents the abs() function end procedure
示例
在下面的程序中,我们使用按位补码运算符和 absolute 函数计算数字加 1 的增量。
#include <bits/stdc++.h> using namespace std; // 用于计算输入值增加 1 的函数 int inc(int num){ // 使用 abs() 函数获取通过 ~ 运算符得到的值 num = abs(~num); return num; } int main(){ int input = 7; cout << input << " + 1 using bit manipulation = " << inc(input); return 0; }
输出
7 + 1 using bit manipulation = 8
时间复杂度 − O(1),因为没有实现循环。
空间复杂度 − O(1),因为没有使用额外的空间。
方法 2
以下面几个二进制加法的例子为例:
从以上例子可以看出,在二进制加法 1 时,数字右边第一个未设置的位左侧的二进制数字保持不变。但是,右边第一个未设置的位会被设置,并且右侧的所有位都会被切换,即设置的位会被设置,未设置的位会被设置(0 转换为 1,1 转换为0)。
因此,我们可以将问题分解为两个基本部分:
设置右侧第一个未设置的位。
切换右侧的所有位。
递归方法伪代码
procedure inc (num) if num & 1 == 0 ans = num | 1 else ans = inc (num >> 1) << 1 end if end procedure
示例
在下面的程序中,我们基于上面解释的思路,使用递归将数字加 1。
#include <bits/stdc++.h> using namespace std; // 用于计算输入增量的函数 int inc(int num){ // 检查 LSB 是否已设置。如果未设置,则进入 if 循环,否则进入 else 循环 if ((num & 1) == 0) { // LSB 已设置 return num | 1; } else { // 将数字右移,并再次调用该函数 // 获取被调用函数的返回值后,将数字左移 return inc(num >> 1) << 1; } } int main(){ int input = 14; cout << input << " + 1 using bit manipulation = " << inc(input); return 0; }
输出
14 + 1 using bit manipulation = 15
时间复杂度 − O(k),其中 k 是从右侧算起第一个未设置位的位置。
空间复杂度 − O(k),因为使用了递归堆栈空间。
迭代方法伪代码
procedure inc (num) m = 1 while num & m num = num & (~m) m = m << 1 end while num = num | m end procedure
示例
在下面的程序中,我们使用一个掩码数,该掩码数在每次迭代中都会发生变化,直到我们找到右侧未设置的位。一旦找到该位,就设置未设置的位,并切换右侧的所有其他位。
#include <bits/stdc++.h> using namespace std; // 用于查找输入增量的函数 int inc(int num){ // 声明一个值为 1 的掩码,用于查找右侧第一个未设置的位 int m = 1; // 检查 LSB 是否已设置。如果已设置,则进入循环并重复,直到找到未设置的位 while (num & m) { num &= ~m; // 每次迭代时都会更改掩码,以在循环结束时设置未设置位和切换位 m <<= 1; } // 用于设置未设置位并切换右侧所有位的掩码 num |= m; return num; } int main(){ int input = 14; cout << input << " + 1 using bit manipulation = " << inc(input); return 0; }
输出
14 + 1 using bit manipulation = 15
时间复杂度 − O(k),其中 k 表示从右侧算起第一个未设置的位的位置。
空间复杂度 − O(1)
方法 3
在此方法中,我们通过减少查找第一个未设置的位的时间来降低时间复杂度。在此方法中,我们首先找到从右侧算起第一个未设置的位,将其设置为 1,然后切换右侧的所有位。
伪代码
procedure inc (num) pos = log2 (num & ~num) add = 1 << pos num = num | add if pos != 0 toggle = (1 << pos) - 1 num = num ^ toggle end if ans = num end procedure
示例
以下程序中,Log2() 运算用于查找未置位的位,左移和"或"运算用于设置该位,左移和"异或"运算用于切换位。
#include <bits/stdc++.h> using namespace std; // 用于查找输入增量的函数 int inc(int num){ // 查找从右侧开始第一个未置位的位的位置 int pos = log2(num & ~num); // 以下两个运算合并设置未置位的位 int add = 1 << pos; num |= add; // 仅对第一个未置位的位不是最低有效位 (LSB) 的数字进行位切换 if (pos != 0){ // 使用左移和异或运算切换位 int toggle = (1 << pos) - 1; num ^= toggle; } return num; } int main(){ int input = 14; cout << input << " + 1 using bit manipulation = " << inc(input); return 0; }
输出
14 + 1 using bit manipulation = 15
时间复杂度 − O(1)
空间复杂度 − O(1)
结论
总而言之,对于数字加 1 的操作,我们可以使用二进制补码的概念,或者通过分析加 1 时位的性质,按照上述方法得到所需的结果。