Python LintCode:A + B问题,最详原理

2024-01-07 02:58

本文主要是介绍Python LintCode:A + B问题,最详原理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Python LintCode:A + B问题,最详原理

    • 概述
    • 如何用高级语言实现
    • 简单说一下
    • 参考链接

概述

该题中要求不使用加法运算符,那么我们就需要思考"+"运算符是如何实现的,我们先看在数电中加法是如何实现的,如下图所示(半加器结构)。
加法器,由一个异或门和与门组成

可以看出,加法器由一个异或门(上) 和一个与门(下) 组成,S为计算和,C为计算中产生的进位。那么这个时候我们就需要考虑使用位运算来完成上述题目。
我们举一个实际的例子来说明(假设用一个byte存储整数):2 + 9

2 + 9(对应于A + B)
2对应的二进制可以表示为0000 0010
9对应的二进制可以表示为0000 1001

正常情况下,我们在做加法的时候,对应位相加,如果有进位则加上进位,计算出最终结果时进位为0。
①对于2 + 9,我们先考虑无进位加法,个位相加和2 + 9 = 1,所以S = 1, 只考虑进位加法:2 + 9 个位产生了进位,所以进位1在十位,个位为0,即C = 10。以此方法可以推广到更高位。
②此时,我们让A = S,B = C,再次计算,个位相加为1 + 0为1,十位相加为0 + 1为1,所以最终结果为11,因为个位和十位均无进位产生,所以C = 0。

我们再举一个实际的例子来说明(十进制数):29 + 73

29 + 73(对应于A + B)
29对应的二进制可以表示为0001 1101
73对应的二进制可以表示为0100 1001

①对于29 + 73,我们先考虑无进位加法,个位相加和9 + 3 = 2,十位相加为2 + 7 = 9,所以S = 92, 只考虑进位加法:9 + 3 个位产生了进位,十位2 + 7并无进位产生,所以C = 10。
②此时,我们让A = S,B = C,再次计算,个位相加为2 + 0为2,十位相加为9 + 1为0,所以最终结果为2,十位产生了进位,所以C = 100。
③再次令A = S,B = C,再次计算,个位相加为2 + 0,十位为0,百位为1,最终结果S = 102,各个位均无进位产生。所以C = 0,此时结束计算,最终结果即为102。
通过上面两个例子可以知道,只要C不为0时,循环执行①②步骤,最终结果即为S。那么既然要用位运算,不可避免的要使用二进制数,那么适用于十进制的方法是否也适合二进制,我们继续采样上述例子(2 + 9):
0000 0010 + 0000 1001在不考虑进位的情况下,其结果为0000 1011,那么这个结果即为二进制下无进位加法,也等价于A ^ B(A与B的异或)
只考虑进位的情况下:观察上述二进制码,我们发现各个位都没有进位,因此C = 0,此时加法的结果位S = 0b1011 = 11

对于29 + 73这个例子
首先考虑无进位加法,0001 1101 + 0100 1001结果为S = 0101 0100(即A ^ B),只考虑进位加法C = 0001 0010,我们发现这个C的值恰好为(a & b) << 1,即A与B相与后,左移一位。
接下来执行A = S,B = C,继续,那么无进位加法S = 0100 0110, C = 0010 0000,继续执行A = S,B = C,无进位加法S = 0110 0110,C = 0000 0000,此时进位为0,那么S即为最终结果S = 0110 0110 = 102。
以上四个例子(2个十进制和2个二进制)主要说明了一个算法的流程,在十进制下如何进行,二进制下如何进行,总结上述方法的过程如下:

对于给定的A B整数值,通过位运算进行加法运算
步骤①:S = A ^ B
步骤②:C = (A & B) << 1
步骤③:如果C不为0,执行A = S,B = C,然后重复执行步骤①和步骤②

如何用高级语言实现

通过概述一栏中的描述,我们已经知道了位运算的这样一个过程,但是请注意,我在举例子的时候提到过我们使用一个byte来存储,我们可以很快的计算出最终的结果(因为C中的左移操作可以很快完成),在C或者C++、Java等高级语言中,int类型值得存储使用4个字节,一共32位,这里用C语言完成上述步骤,非递归方式,递归得话写起来会更简单:

int aplusb(int a, int b) {// a b 可以为任意整数int carry = 0;int sum_ = 0;while(b != 0){carry = (a & b) << 1;sum_ = (a ^ b);a = sum_;b = carry;}return a;}

在Python中,同样代码只能进行正整数与正整数的加和计算,如果A与B存在负数,那么程序会进入死循环,这是由Python对int的储存机制造成,那么我先上正确的代码,然后解释为什么要这么写

def aplusb(a, b):while b != 0:sum_ = a ^ bcarry = (a & b) << 1a = sum_ % 0x100000000b = carry % 0x100000000return a if a <= 0x7FFFFFFF else ~(a ^ 0xFFFFFFFF)

在python中,int类型并不是4 bytes存储,而是24 bytes,可以认为python中的int支持无限长的整型值,这样的话会导致进位在左移时永远不会溢出,也就是永远无法获得最终的结果,所以我们需要手动去模拟边界,32bit的最大值为 2 32 − 1 2^{32} - 1 2321,也就是说32bit的支持的整数长度为 2 32 2^{32} 232,写成二进制为0x100000000,这样的话通过取模运算,就可以将A与B的值限制在32bit可以表示的集合中,那么我们将正整数集合划分为[-0x10000000, 0x7FFFFFFF]这样两部分,可以类似于8bit的范围为[-128, 127],最大值为255,一共可以表示256个数,这样构造结束之后,当C = 0时,只要A的值小于等于最大值0x7FFFFFFF,那么就可以直接返回,而对于大于0x7FFFFFFF,可知其最高位必为1,我们需要对其进行处理,将其符号位提取出来。

再举一个简介明了的例子,比如我们用4 bit来存储一个int值,假如这个值为-2,其二进制为1110,如果我想把这个值转换为8 bit类型的值,应该如何做呢?这里我们需要知道的是,负数的二进制表示为正数的补码形式。
**我们先看-2在8 bit中如何表示:
2的原码:0000 0010
2的反码:1111 1101
2的补码:1111 1110 (8bit中,-2的二进制表示) **

而0000 1110在8bit中对应的值是14,我们需要将其转换,首先将 0000 1110 与 0xF做异或操作,结果为0000 0001,然后取反1111 1110,这样的话,就将4bit中的-2转换为8bit中的-2了,总结下来就两步:
① 将4bit中的结果与0xF异或
② 将异或的结果取反

可以将其扩展至32位,只要将结果与0xFFFFFFFF做异或运算,然后取反即可。如果说这个你没看懂,那我再写一个推理,如下:

1111 1110(8bit,-2的二进制表示) 8bit转16bit

原码:0000 0000 0000 0010 (2的原码)
反码:1111 1111 11111 1101
补码:1111 1111 1111 1110 (16bit的 -2的表示)

0000 0000 1111 1110 (16bit的 -2的表示)
0000 0000 1111 1111 (0xFF)
异或:0000 0000 0000 0001
取反:1111 1111 1111 1110 (8bit转16bit的结果)

1111 1111 1111 1110 (16位 -2的表示) 转8bit的-2 (1111 1110)

1111 1111 1111 1110 (16位 -2的表示)
0000 0000 1111 1111 (0xFF)

异或:1111 1111 0000 0001
取反:0000 0000 1111 1110
结果:1111 1110 与上述结果一致

Python的int存储机制,使得我们要从高位转低位,所以当我们要转换位32bit的表示时,需要与0xFFFFFFFF做异或然后取反即为最终结果。

简单说一下

这道题以前是用C++写的,感觉不是特别难理解,最近用python重新写的时候才发现以前竟然迈过了好多坑,这个题用python来做真的能有很多收获,如果大家有什么问题,欢迎指出来,我一个一个字敲的,难免会有错误。

参考链接

[1] 位运算详解以及在 Python 中需要的特殊处理
[2] Python 位运算一些坑
[3] 位运算实现加、减、乘、除运算

这篇关于Python LintCode:A + B问题,最详原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/578497

相关文章

Python基础文件操作方法超详细讲解(详解版)

《Python基础文件操作方法超详细讲解(详解版)》文件就是操作系统为用户或应用程序提供的一个读写硬盘的虚拟单位,文件的核心操作就是读和写,:本文主要介绍Python基础文件操作方法超详细讲解的相... 目录一、文件操作1. 文件打开与关闭1.1 打开文件1.2 关闭文件2. 访问模式及说明二、文件读写1.

Python将博客内容html导出为Markdown格式

《Python将博客内容html导出为Markdown格式》Python将博客内容html导出为Markdown格式,通过博客url地址抓取文章,分析并提取出文章标题和内容,将内容构建成html,再转... 目录一、为什么要搞?二、准备如何搞?三、说搞咱就搞!抓取文章提取内容构建html转存markdown

Python获取中国节假日数据记录入JSON文件

《Python获取中国节假日数据记录入JSON文件》项目系统内置的日历应用为了提升用户体验,特别设置了在调休日期显示“休”的UI图标功能,那么问题是这些调休数据从哪里来呢?我尝试一种更为智能的方法:P... 目录节假日数据获取存入jsON文件节假日数据读取封装完整代码项目系统内置的日历应用为了提升用户体验,

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Python Websockets库的使用指南

《PythonWebsockets库的使用指南》pythonwebsockets库是一个用于创建WebSocket服务器和客户端的Python库,它提供了一种简单的方式来实现实时通信,支持异步和同步... 目录一、WebSocket 简介二、python 的 websockets 库安装三、完整代码示例1.

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

Python使用自带的base64库进行base64编码和解码

《Python使用自带的base64库进行base64编码和解码》在Python中,处理数据的编码和解码是数据传输和存储中非常普遍的需求,其中,Base64是一种常用的编码方案,本文我将详细介绍如何使... 目录引言使用python的base64库进行编码和解码编码函数解码函数Base64编码的应用场景注意