位运算03 不用加号的加法[C++]

 

图源:文心一言

上机题目练习整理,位运算,供小伙伴们参考~🥝🥝

网页版目录在页面的右上角↗~🥝🥝

  • 第1版:在力扣新手村刷题的记录~🧩🧩

编辑:梅头脑🌸

审核:文心一言

题目:面试题 17.01. 不用加号的加法 - 力扣(LeetCode)


🧵面试题 17.01. 不用加号的加法

🧩题目

设计一个函数把两个数字相加。不得使用 + 或者其他算术运算符。

示例:

输入: a = 1, b = 1
输出: 2

提示:

  • ab 均可能是负数或 0
  • 结果不会溢出 32 位整数

🌰解题一:循环+按位运算(一)

📇算法思路

  • 算法思想:
    • 初始化:函数接收两个整数ab作为输入。

    • 循环条件:当进位b不为0时,循环继续。

    • 计算进位:使用a & b来计算ab的哪些位都需要进位(仅当 a 为 1,且 b 为 1时,需要进位)。然后,通过左移一位(<< 1)来将这些进位应用到正确的位置上。这里使用unsigned int是为了确保左移操作不会引入任何符号位。

    • 计算本位和:使用a ^ b(异或操作来计算不考虑进位时的和。异或操作会返回在两个输入中只有一个为1的那些位上设置1的结果,例如:

      • a = 1, b = 1 ,算数运算:a + b = 10,位运算:本位和为 1 ^ 1 = 0,进位为 1 & 1 = 1;

      • a = 1, b = 0 ,算数运算:a + b =  01,位运算:本位和为 1 ^ 0 = 1,进位为 1 & 0 = 0;

      • a = 0, b = 1 ,算数运算:a + b =  01,位运算:本位和为 0 ^ 1 = 1,进位为 0 & 1 = 0;

      • a = 0, b = 0 ,算数运算:a + b =  00,位运算:本位和为 0 ^ 0 = 0,进位为 0 & 0 = 0;

    • 更新操作数

      • a被更新为不考虑进位的本位和(a ^ b的结果)。
      • b被更新为进位((unsigned int)(a & b) << 1的结果)。这样,在下一次循环中,b将包含所有需要被加到a上的进位。
    • 返回结果:当b变为0(即没有更多的进位需要处理)时,循环结束,函数返回a作为最终的和。

  • 时间复杂度:O(1)或O(log(max(a, b)));我们可以认为时间复杂度是O(1),其中1表示一个固定的上限,即整数的大小,或整数的二进制表示的长度。
  • 空间复杂度:O(1);

⌨️算法代码

#include <iostream>
#include <bitset>
using namespace std;class Solution {
public:int add(int a, int b) {while (b != 0) {unsigned int carry = (unsigned int)(a & b) << 1;a = a ^ b;b = carry;}return a;}
};//class Solution {
//public:
//    int add(int a, int b) {
//        cout << "a = " << a << " = " << bitset<32>(a) << ", b = " << b << " = " << bitset<32>(b) << endl;
//
//        while (b != 0) {
//            unsigned int carry = (unsigned int)(a & b) << 1;
//            cout << "carry = " << bitset<32>(carry);
//            a = a ^ b;
//            cout << ", a = " << bitset<32>(a);
//            b = carry;
//            cout << ", b = " << bitset<32>(b) << endl;
//        }
//        return a;
//    }
//};
//
//int main()
//{   
//    int a = 111;  int b = 899;
//    Solution solution;
//    int result = solution.add(a, b);
//    cout << "result = " << result << " = " << bitset<32>(result) << endl;
//
//    return 0;
//}

作者:力扣官方题解
链接:https://leetcode.cn/problems/add-without-plus-lcci/solutions/1718025/bu-yong-jia-hao-de-jia-fa-by-leetcode-so-pp8a/

📇执行结果

输入 a = 111, b = 899后,执行结果如下:

📇代码解释

1:输入 a 与 b

  • a =  111(十进制) =  00000000000000000000000001101111(二进制)
  • b =  899(十进制) =  00000000000000000000001110000011(二进制)
  • 本轮需要进位的在第0位、第1位,11 + 11 = 110;

2:计算第1轮 本位和与进位

  • 进位 carry a & b = 00000000000000000000000000000011,无符号数算数左移1位结果为 000000000000000000000000000000110
  • 本位和 a : a ^ b = 00000000000000000000001111101100;
  • 进位 bb = carry = 000000000000000000000000000000110;
  • 本轮需要进位的在第2位,100 + 100 = 1000;

3:计算第2轮 本位和与进位

  • 进位 carry a & b = 00000000000000000000000000000100,无符号数算数左移1位结果为 00000000000000000000000000001000;
  • 本位和 a : a ^ b = 00000000000000000000001111101010;
  • 进位 bb = carry = 00000000000000000000000000001000;
  • 本轮需要进位的在第3位,1000 + 1000 = 10000;

4:计算第3轮 本位和与进位

  • 进位 carry a & b = 00000000000000000000000000001000,无符号数算数左移1位结果为 00000000000000000000000000010000;
  • 本位和 a : a ^ b = 00000000000000000000001111100010;
  • 进位 bb = carry = 00000000000000000000000000010000;
  • 本轮 a 与 b 没有同时为 1 的位,最后进行异或,进位b更新为0,本位和a即为答案;

5:计算第4轮 本位和与进位

  • 进位 carry a & b = 00000000000000000000000000000000,无符号数算数左移1位结果为 00000000000000000000000000000000;
  • 本位和 a : a ^ b = 00000000000000000000001111110010;
  • 进位 bb = carry = 00000000000000000000000000000000;
  • 返回:a = 1010 (十进制) = 00000000000000000000001111110010(二进制);

这道题目的难点应该是从后向前一轮一轮地处理 进位,(本位和异或就可以搞定了),感觉官方的解法真的很简洁美观,但是以我的功底,要能自己写出这种水平的代码,感觉至少还要1年,囧...

🌰解题二:循环+按位运算(二)(简单全加器原理)

📇算法思路

  • 算法思想:
    • 初始化:函数接收两个整数ab作为输入。

    • 循环条件:int 通常为 32位,未读取完32位时,循环继续遍历读取每1位。

    • 全加器逻辑

      • 每次从ab中提取一位(从最低位开始)记入 bita、bitb进行运算,调用函数fulladder计算本位和 sum 与 进位 carryout,计算结果存入容器 adderresult。

      • 计算进位:使用a & b来计算ab的哪些位都需要进位(仅当 a 为 1,且 b 为 1时,需要进位)。然后,通过左移一位(<< 1)来将这些进位应用到正确的位置上。这里使用unsigned int是为了确保左移操作不会引入任何符号位。

      • 计算本位和:使用 sum = a ^ b异或操作来计算不考虑进位时的和。异或操作会返回在两个输入中只有一个为1的那些位上设置1的结果;

    • 更新操作数:在result 中输出本位和与进位;

    • 返回结果:当int遍历结束时时,循环结束,函数返回result作为最终的和。

  • 时间复杂度:O(1):总是处理32位整数;
  • 空间复杂度:O(1):使用了固定数量的变量来存储中间结果和进位;

⌨️算法代码

#include <iostream>  
#include <utility> // 为了使用 std::pair  // 全加器函数,返回一个包含和与进位的pair  
std::pair<int, int> fulladder(int a, int b, int carryin) {int sum = a ^ b ^ carryin; // 不考虑进位的和  int carryout = (a & b) | ((a ^ b) & carryin); // 进位,注意这里修正了逻辑错误  return std::make_pair(sum, carryout); // 返回和与进位的pair  
}// 两个32位整数相加  
int add(int a, int b) {int carry = 0; // 初始化进位为0  int result = 0; // 初始化结果为0  for (int i = 0; i < 32; ++i) { // 逐位相加  int bita = (a >> i) & 1; // a的第i位  int bitb = (b >> i) & 1; // b的第i位  // 调用全加器并更新结果和进位  std::pair<int, int> adderresult = fulladder(bita, bitb, carry);result |= adderresult.first << i; // 将当前位的和加入结果中  carry = adderresult.second; // 更新进位为全加器的进位输出  }// 注意:此实现不处理溢出情况。在实际应用中,应考虑溢出。  return result;
}int main() {int a = 111; // 101 in binary, 但实际上在32位整数中是 0000 ... 0101  int b = 899; // 011 in binary, 但实际上在32位整数中是 0000 ... 0011  int expected = 1010; // 1000 in binary, 但实际上在32位整数中是 0000 ... 1000  int actual = add(a, b); // 应该返回1010(没有溢出的情况下)  std::cout << "expected: " << expected << std::endl; // 输出期望结果  std::cout << "actual: " << actual << std::endl; // 输出实际结果,应该是8  return 0; // 程序成功执行完毕,返回0表示成功状态码  
}

作者:文心一言 

📇执行结果

测试代码

int add(int a, int b) {int carry = 0;int result = 0;for (int i = 0; i < 32; ++i) {int bita = (a >> i) & 1;int bitb = (b >> i) & 1;std::pair<int, int> adderresult = fulladder(bita, bitb, carry);// 添加打印语句来查看中间变量的值  std::cout << "Iteration(位数) " << i << ":" << std::endl;std::cout << "  bita: " << bita << std::endl;std::cout << "  bitb: " << bitb << std::endl;std::cout << "  carry(本轮进位): " << carry << std::endl;std::cout << "  adderresult (sum, carry): ("<< adderresult.first << ", " << adderresult.second << ")" << std::endl;result |= adderresult.first << i;carry = adderresult.second;// 打印当前的结果和进位  std::cout << "  result: " << result << " = " << std::bitset<32>(result) <<std::endl;std::cout << "  carry(下轮进位): " << carry << std::endl;std::cout << std::endl; // 打印空行以便于阅读  }return result;
}

运行结果 

1:输入 a 与 b

  • a =  111(十进制) =  00000000000000000000000001101111
  • b =  899(十进制) =  00000000000000000000001110000011

2:代码运行

Iteration(位数) 0:
  bita: 1
  bitb: 1
  carry(本轮进位): 0
  adderresult (sum, carry): (0, 1)
  result: 0 = 00000000000000000000000000000000
  carry(下轮进位): 1

Iteration(位数) 1:
  bita: 1
  bitb: 1
  carry(本轮进位): 1
  adderresult (sum, carry): (1, 1)
  result: 2 = 00000000000000000000000000000010
  carry(下轮进位): 1

Iteration(位数) 2:
  bita: 1
  bitb: 0
  carry(本轮进位): 1
  adderresult (sum, carry): (0, 1)
  result: 2 = 00000000000000000000000000000010
  carry(下轮进位): 1

Iteration(位数) 3:
  bita: 1
  bitb: 0
  carry(本轮进位): 1
  adderresult (sum, carry): (0, 1)
  result: 2 = 00000000000000000000000000000010
  carry(下轮进位): 1

Iteration(位数) 4:
  bita: 0
  bitb: 0
  carry(本轮进位): 1
  adderresult (sum, carry): (1, 0)
  result: 18 = 00000000000000000000000000010010
  carry(下轮进位): 0

Iteration(位数) 5:
  bita: 1
  bitb: 0
  carry(本轮进位): 0
  adderresult (sum, carry): (1, 0)
  result: 50 = 00000000000000000000000000110010
  carry(下轮进位): 0

Iteration(位数) 6:
  bita: 1
  bitb: 0
  carry(本轮进位): 0
  adderresult (sum, carry): (1, 0)
  result: 114 = 00000000000000000000000001110010
  carry(下轮进位): 0

Iteration(位数) 7:
  bita: 0
  bitb: 1
  carry(本轮进位): 0
  adderresult (sum, carry): (1, 0)
  result: 242 = 00000000000000000000000011110010
  carry(下轮进位): 0

Iteration(位数) 8:
  bita: 0
  bitb: 1
  carry(本轮进位): 0
  adderresult (sum, carry): (1, 0)
  result: 498 = 00000000000000000000000111110010
  carry(下轮进位): 0

Iteration(位数) 9:
  bita: 0
  bitb: 1
  carry(本轮进位): 0
  adderresult (sum, carry): (1, 0)
  result: 1010 = 00000000000000000000001111110010
  carry(下轮进位): 0

……

Iteration(位数) 31:
  bita: 0
  bitb: 0
  carry(本轮进位): 0
  adderresult (sum, carry): (0, 0)
  result: 1010 = 00000000000000000000001111110010
  carry(下轮进位): 0

expected: 1010
actual: 1010

🌰自留错误版本

📇算法思路

  • 基本思想同算法一,通过计算本位和与进位计算结果~

⌨️算法代码(错误,自留代码)

#include <iostream>
#include <bitset>
using namespace std;class Solution {
public://代码逻辑有问题, sum += 那一行,违背了位运算原则。int add(int a, int b) {int sum = 0;int carry = 0;int i = 0;for (i = 0; i < 32; i++) {cout << "本轮 i = " << i << endl;int bitA = (a >> i) & 1;cout << "本轮 bitA = " << bitA << endl;int bitB = (b >> i) & 1;cout << "本轮 bitB = " << bitB << endl;int bitSum = bitA ^ bitB;cout << "本轮 bitSum = " << bitSum << endl;carry = (bitA & bitB) | (bitSum & carry);cout << "本轮 carry = " << carry << endl;sum = sum + (carry << i + 1) + (bitSum << i);cout << "本轮 sum = " << sum << endl;carry <<= 1;cout << "carry 左移一位" << endl;cout << endl;}return sum;}//int add(int a, int b) {//    int result = 0;//    int carry = 0;//    int i = 0;//    for (i = 0; i < 32; i++) {//        cout << "本轮 i = " << i << endl;//        int bitA = (a >> i) & 1;//        cout << "本轮 bitA = " << bitA << endl;//        int bitB = (b >> i) & 1;//        cout << "本轮 bitB = " << bitB << endl;//        int bitSum = bitA ^ bitB;//        cout << "本轮 bitSum = " << bitSum << endl;//        carry = (bitA & bitB) | (bitSum & carry);//        cout << "本轮 carry = " << carry << endl;//        // sum = sum + (carry << i + 1) + (bitSum << i);//        result = result | bitSum << i;//        if (carry & 1) { result |= (1 << (i + 1));}//        cout << "本轮 result = " << result << endl;//        carry >>= 1;//        cout << "carry 右移一位" << endl;//        cout << endl;//    }//    return result;//}};int main()
{   //int a = 1;  int b = 1;int a = 111;  int b = 899;Solution solution;int result = solution.add(a, b);cout << result;return 0;
}

作者:梅头脑

这个解法呃,倒是可以运行,将 本位和 与 进位和分开了,接近解法二的思路。但是因为没能很好得处理进位的问题,就使用了加法企图蒙混过关 sum = sum + (carry << i + 1) + (bitSum << i); (此处如果使用 “ | ” 会吞掉进位与本位的相同位),违背了题目不能使用加法的本意 。又想到使用result,也出现了类似的问题,后来摆脱文心一言用全加器的原理修改掉,也就是解法二~~


🔚结语

博文到此结束,写得模糊或者有误之处,欢迎小伙伴留言讨论与批评,督促博主优化内容{例如有错误、难理解、不简洁、缺功能}等,博主会顶锅前来修改~~😶‍🌫️😶‍🌫️

我是梅头脑,本片博文若有帮助,欢迎小伙伴动动可爱的小手默默给个赞支持一下,感谢点赞小伙伴对于博主的支持~~🌟🌟

同系列的博文:🌸位运算_梅头脑_的博客-CSDN博客

同博主的博文:🌸随笔03 笔记整理-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/264819.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

二叉树与堆

目录 1.树概念及结构 1.1树的概念 1.2 树的相关概念 1.3 树的表示 1.4 树在实际中的运用&#xff08;表示文件系统的目录树结构&#xff09; 2.二叉树概念及结构 2.1概念 2.2现实中的二叉树&#xff1a; 2.3 特殊的二叉树&#xff1a; 2.4 二叉树的性质 2.5 二叉树的…

高性能 Kafka 及常见面试题

Kafka 是一种分布式的&#xff0c;基于发布/订阅的消息系统&#xff0c;原本开发自 LinkedIn&#xff0c;用作 LinkedIn 的事件流&#xff08;Event Stream&#xff09;和运营数据处理管道&#xff08;Pipeline&#xff09;的基础。 基础原理详解可见 Kafka 基本架构及原理 基础…

【大数据】Flink SQL 语法篇(四):Group 聚合、Over 聚合

Flink SQL 语法篇&#xff08;四&#xff09;&#xff1a;Group 聚合、Over 聚合 1.Group 聚合1.1 基础概念1.2 窗口聚合和 Group 聚合1.3 SQL 语义1.4 Group 聚合支持 Grouping sets、Rollup、Cube 2.Over 聚合2.1 时间区间聚合2.2 行数聚合 1.Group 聚合 1.1 基础概念 Grou…

019 Spring Boot+Vue 电影院会员管理系统(源代码+数据库+文档)

部分代码地址&#xff1a; https://github.com/XinChennn/xc019-cinema 一、系统介绍 cinema项目是一套电影院会员管理系统&#xff0c;使用前后端分离架构开发包含管理员、会员管理、会员卡管理、电影票、消费记录、数据统计等模块 二、所用技术 后端技术栈&#xff1a; …

【Flink精讲】Flink组件通信

主要指三个进程中的通讯 CliFrontendYarnJobClusterEntrypointTaskExecutorRunner Flink内部节点之间的通讯使用Akka&#xff0c;比如JobManager和TaskManager之间。而operator之间的数据传输是利用Netty。 RPC是统称&#xff0c;Akka&#xff0c;Netty是实现 Akka与Ac…

热闹元宵进行中,如何利用VR全景展示民宿品牌形象?

错峰出游闹元宵&#xff0c;元宵节恰逢周末&#xff0c;而且还是春节假期返工之后的首个休息日&#xff0c;不少人都想通过短途度假来缓解“节后综合征”。两位数的特价机票、打折的各种酒店让你实现“旅行自由”&#xff0c;那么如何知道特价酒店服务好不好呢&#xff1f;先别…

Docker Volume

"Ice in my vein" Docker Volume(存储卷) 什么是存储卷? 存储卷就是: “将宿主机的本地文件系统中存在的某个目录&#xff0c;与容器内部的文件系统上的某一目录建立绑定关系”。 存储卷与容器本身的联合文件系统&#xff1f; 在宿主机上的这个与容器形成绑定关系…

实用区块链应用:去中心化投票系统的部署与实施

一、需求分析背景 随着技术的发展&#xff0c;传统的投票系统面临着越来越多的挑战&#xff0c;如中心化控制、透明度不足和易受攻击等问题。为了解决这些问题&#xff0c;我们可以利用区块链技术去中心化、透明性和安全性来构建一个去中心化投票系统。这样的系统能够确保投票过…

vue2.0及起步(前端面试知识积累)

1、需要了解的vue概要知识 1、vue是什么&#xff1f; 一套用于构建用户界面的渐进式JavaScript框架。 为什么vue被称为是渐进式JS框架&#xff1f; 答&#xff1a;Vue允许开发者在不同的项目中以渐进式的方式使用它&#xff0c;这种渐进式表现在以下的方面&#xff1a; 逐步采…

数据可视化--了解数据可视化和Excel数据可视化

目录 1.1科学可视化&#xff1a; 可视化是模式、关系、异常 1.2三基色原理&#xff1a; 三基色:红色、绿色和蓝色 1.3Excel数据可视化 1.3.1 excel数据分析-13个图表可视化技巧 1.3.2 excel数据分析-28个常用可视化图表&#xff08;video&#xff09; 1.3.3Excel可视化…

Java面试——锁

​ 公平锁&#xff1a; 是指多个线程按照申请锁的顺序来获取锁&#xff0c;有点先来后到的意思。在并发环境中&#xff0c;每个线程在获取锁时会先查看此锁维护的队列&#xff0c;如果为空&#xff0c;或者当前线程是等待队列的第一个&#xff0c;就占有锁&#xff0c;否则就会…

Apache Doris 发展历程、技术特性及云原生时代的未来规划

本文节选自《基础软件之路&#xff1a;企业级实践及开源之路》一书&#xff0c;该书集结了中国几乎所有主流基础软件企业的实践案例&#xff0c;由 28 位知名专家共同编写&#xff0c;系统剖析了基础软件发展趋势、四大基础软件&#xff08;数据库、操作系统、编程语言与中间件…

js里面有引用传递吗?

一&#xff1a;什么是引用传递 引用传递是相对于值传递的。那什么是值传递呢&#xff1f;值传递就是在传递过程中再复制一份&#xff0c;然后再赋值给变量&#xff0c;例如&#xff1a; let a 2; let b a;在这个代码中&#xff0c;let b a; 就是一个值传递&#xff0c;首先…

深度学习手写字符识别:推理过程

说明 本篇博客主要是跟着B站中国计量大学杨老师的视频实战深度学习手写字符识别。 第一个深度学习实例手写字符识别 深度学习环境配置 可以参考下篇博客&#xff0c;网上也有很多教程&#xff0c;很容易搭建好深度学习的环境。 Windows11搭建GPU版本PyTorch环境详细过程 数…

设计模式(十) - 工厂方式模式

前言 在此前的设计模式&#xff08;四&#xff09;简单工厂模式中我们介绍了简单工厂模式&#xff0c;在这篇文章中我们来介绍下工厂方法模式&#xff0c;它同样是创建型设计模式&#xff0c;而且又有些类似&#xff0c;文章的末尾会介绍他们之间的不同。 1.工厂方法模式简介 …

小程序性能优化

背景 在开发小程序的过程中我们发现&#xff0c;小程序的经常会遇到性能问题&#xff0c;尤其是在微信开发者工具的时候更是格外的卡&#xff0c;经过排查发现&#xff0c;卡顿的页面有这么多的js代码需要加载&#xff0c;而且都是在进入这个页面的时候加载&#xff0c;这就会…

面试redis篇-10Redis集群方案-主从复制

在Redis中提供的集群方案总共有三种: 主从复制哨兵模式分片集群主从复制 单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。 主从数据同步原理 Replication Id:简称replid,是数据集的标记,id一致则说明是同一数据集。每…

React18源码: Fiber树的初次创建过程图文详解

fiber树构造&#xff08;初次创建&#xff09; fiber树构造的2种情况&#xff1a; 1.初次创建 在React应用首次启动时&#xff0c;界面还没有渲染此时并不会进入对比过程&#xff0c;相当于直接构造一棵全新的树 2.对比更新 React应用启动后&#xff0c;界面已经渲染如果再次发…

软考45-上午题-【数据库】-数据操纵语言DML

一、INSERT插入语句 向SQL的基本表中插入数据有两种方式&#xff1a; ①直接插入元组值 ②插入一个查询的结果值 1-1、直接插入元组值 【注意】&#xff1a; 列名序列是可选的&#xff0c;若是所有列都要插入数值&#xff0c;则可以不写列名序列。 示例&#xff1a; 1-2、插…

跟着cherno手搓游戏引擎【26】Profile和Profile网页可视化

封装Profile&#xff1a; Sandbox2D.h:ProfileResult结构体和ProfileResult容器&#xff0c;存储相应的信息 #pragma once #include "YOTO.h" class Sandbox2D :public YOTO::Layer {public:Sandbox2D();virtual ~Sandbox2D() default;virtual void OnAttach()ove…