通过操作位将数字加 1

data structurec++programming

位操作使用位运算符(例如 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 时位的性质,按照上述方法得到所需的结果。


相关文章