不用加减乘除做加法

256 阅读4分钟

「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战

前言

笔者除了大学时期选修过《算法设计与分析》和《数据结构》还是浑浑噩噩度过的(当时觉得和编程没多大关系),其他时间对算法接触也比较少,但是随着开发时间变长对一些底层代码/处理机制有所接触越发觉得算法的重要性,所以决定开始系统的学习(主要是刷力扣上的题目)和整理,也希望还没开始学习的人尽早开始。

系列文章收录《算法》专栏中。

问题描述

不用加减乘除做加法 写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。

示例:

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

提示:

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

确定学习目标

对《不用加减乘除做加法》的算法过程进行剖析。

剖析

原始十进制分析

其实这个算法也是没有学习成本的,除了简单的位运算和移位没有其他新知识。可以先想下我们平时进行十进制的时候是怎么进行的,如52+28:

  1. 对个位进行相加:2+8=10
  2. 得出个位变成0,得到一个进位1并且进位在十位上。
  3. 我们把十位上的1缓存在大脑中
  4. 对十位进行相加:5+2=7,但是发现大脑中还缓存了十位1,所以十位变成7+1=8
  5. 完成了计算结果等于80。

我们可以总结下:

  1. 先抛去进位的影响,各个位的相加是不是很简单直接相加,但是如果出现进位的时候位相加的和怎么处理?比如上面的变成53+28重复上面的步骤,有没有发现抛去进位的影响我们得出的各个位上的数字为各个位对进制的取余。
  2. 如果按照总结1,上面我们的结果为70,和真实的结果相差10,为啥呢?因为我们抛去了进位的影响,那么我们把进位1给加上,十位上的进位1就是10,那么把70+10,按照结论1的我们是不是就得到了真实的结果。

结论

我们再总结点,得出下面的步骤:

  1. 各个位忽略进位得出结果的算法为:各个位相加对进制取余。
  2. 把进位影响加进来的算法为:各位的结果=进位的结果+各位的结果。

计算机的处理

计算机只认识0和1,所以表示数字用的是二进制。我们利用上面对十进制的结论,发现基本符合,但是由于二进制只能表示0和1,所以在结论的步骤2中还可能出现进位,没关系我们继续重复步骤2把进位的影响加进来,直到没有进位。

我们先考虑下以下两个问题

二进制取余怎么处理?

使用位运算异或就行即^

二进制进位怎么处理?

使用位运算与可以得出每位的进位数字,但是进位的结果需要往左移动1位。

结论

我们可以得出下面步骤(两数相加设为a和b):

  1. a和b异或位运算得出每位抛去进位的结果。
  2. a和b与位运算再左移1位,得出进位结果
  3. 设进位结果为b,a为每位抛去进位的结果,如果b!=0就重复步骤1和2。

代码

根据上面的结论写出以下代码:

/**
 * 1. a和b异或位运算得出每位抛去进位的结果。
 * 2. a和b与位运算再左移1位,得出进位结果
 * 3. 设进位结果为b,a为每位抛去进位的结果,如果b!=0就重复步骤1和2。
 * <p>
 * 溢出的话就是只表示允许表示的位的结果(比如int 直接从右往左取32位)
 *
 * @param a
 * @param b
 * @return
 */
public static int add(int a, int b) {
    int sum = a;
    while (b != 0) {
        sum = a ^ b;
        int carry = (a & b) << 1;
        a = sum;
        b = carry;
    }

    return sum;
}