LeetCode之路:371. Sum of Two Integers

来源:互联网 发布:邮政网络银行 编辑:程序博客网 时间:2024/06/07 22:03

一、引言

这道题非常小巧,题干非常简洁:

Calculate the sum of two integers a and b, but you are not allowed to use the operator + and -.

Example:
Given a = 1 and b = 2, return 3.

翻译下,也就是一句话的事:计算两个整型数的和,要求不能使用 +- 运算符。

看到这里,最直接的想法应该就是,这是一道考察位运算的问题,接下来我们来一步一步解决这个问题。

二、让我们尝试着计算下两个二进制整数的和

既然要计算和了,那么我们也不能光想不实际算算,让我们实际看看用纸笔计算两个整型数的和需要哪些步骤:

1. 在草稿纸上写好两个二进制数

写算式

如图,我们首先将两个数转换成二进制数写在草稿纸上,注意一位一位对齐。

2. 我们就像计算十进制数的加法一样

计算结果
如图,我们就像计算十进制数的加法一样,我们首先从最右边的开始,此时是 1 加 1,那么结果是 0 并且向前面进了 1 位,我们就在前面一位的上面标一个进位 1。如此这般计算下去,就得到了最后的结果。

这里建议读者一定要自己用纸和笔自己试着算一下二进制数的加法,这会对你进行这道题的解答有启发意义。

三、用程序模拟我们刚才的计算过程

通过实际的纸笔运算,我们已经了解了二进制整数的加法是如何进行的了。那么我们来总结下,究竟是如何进行的?

首先,我们计算同样位置的数的和,如果大于 2 ,则进 1 位,前 1 位标 1(想想我们可以怎么样模拟进位操作);

然后,我们到下一个位置上,进行同样的计算过程,如果大于 2 ,则进位,否则就不用进位直接相加(想想我们如何模拟直接相加操作);

最后,当我们算到了没有进位并且当前位置上的数相加为 0 时,则计算结束(同样想想我们该如何判定循环结束)。

如果你真的认真思考了的话,那么我们来一起探讨下,如何模拟上述的过程:

1. 模拟进位操作

我们看看进位操作的本质是什么?

同样位置上的数有两个相同的 1,然后向前位标 1。

想到了什么了吗?我们只需要进行按位与操作即可得到当前所有的同样位置有两个 1 的位,然后我们将结果左移 1 位即可实现这个操作。

2. 模拟直接相加操作

同样,我们来看看直接相加(不产生进位)操作的本质是什么?

我们仅仅是作了相加操作而已,同样位置上要么都是 0, 要么是 1 和 0(1 和 1 的情况被进位操作代替),然后我们直接算出了结果。

这个很容易模拟,我们只需要按位异或即可,当相同为 0,不同为 1。

3. 还有一步,我们如何合并进位的结果与直接相加的结果呢

现在是问题的关键了,我们只是算出了进位的结果,也算出了直接相加的结果,我们现在需要进行两者的结果的合并。

但是,现在问题来了,相加的结果可能还会出现同一位置上有两个 1 的情况,那怎么办呢?

很简单,我们继续回到第 1 步和第 2 步循环,我们可以将进位后的值赋值给第 1 个操作数,直接相加后的值赋值给第 2 个操作数。

到这里,很多人可能就想不明白了,那么什么时候才能算出最终的结果呢?

那么这里,我们要思考问题的关键是什么?

其实问题的关键就是我们何时能够判定循环结束,判定可以出结果。

让我们想想,当进位的结果和直接相加的结果都有了,我们是如何合并两者进行最终值的计算的呢?

我们是将这两个结果的同一位置每每进行对比,看是否全部都是可直接相加的(两个 0 或者 一个 1 和一个 0),如果出现了不可以直接相加还需要进位的(两个 1 )的情况出现,那么我们就无法进行直接的计算,需要进行循环进位合并结果,再看是否可以直接相加。

直到,直到我们可以直接相加,也就是用异或可以得到结果的时候,此时循环才算结束。

那么,代码已出:

// my solution , runtime = 0 msclass Solution {public:    int getSum(int a, int b) {        int temp = 0, sum = 0;        do {            sum = ((a & b) << 1) ^ (a ^ b);            temp = (a & b) << 1;            b = a ^ b;            a = temp;        } while (a & b);        return sum;    }};

思路都是上面讨论的思路,这里值得注意的是,这里我是如何判定出循环的呢?

当最后进位值和直接相加值可以直接合并的时候,也就是每个位上只有 0 和 0 或者 1 和 0 的时候,那么此时二者相与,必为 0;但是只要有一个位上有两个 1 ,二者相与就不为 0 值。

四、追求代码的极致简洁

当然了,没做完一道 LeetCode 上面的题目,你做出来答案可能只收获了一半,真正的收获可能是别人写的最高票答案:

// perfect solution , runtime = 0 msclass Solution {public:    int getSum(int a, int b) {        int sum = a;        while (b != 0) {            sum = a ^ b;            b = (a & b) << 1;            a = sum;        }        return sum;    }};

这里可以看到,最高票答案也是用的同样的思路,只是代码更加简练。

这里,作者的判出条件跟我的不一样。

我是考虑的是合并进位值和直接相加值的相与值为 0 判出,而他则是考虑进位值为 0 判出,其实他这样的思路更加适合理解。

这里简要解释下作者的思路:首先,sum 存放每次循环中 a 与 b 的异或值,也就是直接相加值;b 存放每次的进位值,然后 a 存储 sum (也就是直接相加值)进入下一次循环(当进位值非空);当且仅当进位值为空时,用户的上一次循环中的 sum 已经是可以直接相加的异或结果了,此时得到结果,返回。

五、总结

又见位操作的题目。

对于位操作的题目,我建议是不要死记硬背,我们可以在有过实际的推理和思考经验后,稍微记住几个死的定理,但是理解才是最重要的。

程序的设计过程,其实非常严谨的体现了我们的思考过程,程序写的乱,也就证明我们的思路非常紊乱。

尽管题目简单,也需要认真分析,认真思考,这才是提高程序编写能力的唯一途径。

To be Stronger!

六、一点点福利补充

其实一直记着的,但是写完博客后加上工作又比较忙,又给搞忘了。

这道题的最高票答案有一份非常详尽的关于位操作的整理笔记,对于我们的技能提升非常有帮助(只不过全是英文的),这里附上地址:

A summary: how to use bit manipulation to solve problems easily and efficiently。

0 0
原创粉丝点击