不用加减乘除做加法(Leetcode 剑指Offer65)

1

题目分析

   这个题目很有趣,虽然是一个简单题目,但是做起来是非常有难度的,对于理解计算机数据存储有重要的意义,我认为难度应该是中等以上,小伙伴们也不能轻视它。

位运算

我们先考虑正数的相加问题,用下图进行说明。
q

那么负数如何计算呢,首先要明白负数为了计算的方便在计算机中是以补码的形式保存起来的。相关内容可以参考位运算(Bit Operation)。计算过程也用一张图进行说明。
q

诡异的事情发生了,在Python中没有位的概念,因此负数的补码需要通过其他手段获得。如C++中,对于8位的有符号整型,首位是符号位,将其他的位取反加1可以得到补码,但是Python没有位的概念,也就没有首位,更无法将其他位取反。这应该怎么考虑呢?

因为题目中数据的范围不会超出32位,因此我们将a和b都和0xffffffff做与运算,这样就可以保证超过32位的位都是0,但是此时正数数k与0xffffffff还是原始k,而负数k就会得到0xffffffff + k - 1。在Python中k = -1,k & 0xffffffff的结果是0xffffffff + (-1) - 1。这是因为-1代表全1,做与运算后,得到前32个1,会认为一个正数,因此等于0xffffffff。

上面这段话是说明我们只要和0xffffffff做与操作即可,不需要过分关注于与后的结果。我一开始就出现了这个错误,当我计算-28的时候,结果是4294967268,我一直在纠结为什么要用这个结果参与计算。其实这一步的操作只是为了取负数的前32位,这时Python的特性,会导致将前32位的数当作一个正数来计算,对于位运算来说并没有影响。

然后最终的结果res我们要判断首尾是否位1,$res \le 0xffffffff$说明首位为0,代表正数,直接输出即可,否则说明首位为1,代表负数,但是此时res为32位的,在Python中一定为正数,我们需要在首位加1,并且在高位也都补上1。举个例子,假如最终的结果为15,则res = 0000 0000 0000 0000 0000 0000 0000 1111,在Python中的15也是这样的表达。但是如果最终的结果为-15,则res = 1111 1111 1111 1111 1111 1111 1111 0001,但是Python中的-15不是这样的,Python没有位的概念,可以认为在前面还有无数个1。因此res在Python中的值为4294967281。我们要保留前32位的值,并且给前面都加上1,这样才能得到正确结果。异或1能达到取反的效果,因此我们可以异或0xffffffff然后再取反操作,这样就可以实现高于32位的数据只取反,低于32位数据不变,达到我们想要的效果

1
2
3
4
5
6
7
8
9
10
11
12
class Solution(object):
def add(self, a, b):
"""
:type a: int
:type b: int
:rtype: int
"""
x = 0xffffffff
a, b = a & x, b & x
while b != 0:
a, b = (a ^ b), (a & b) << 1 & x
return a if a <= 0x7fffffff else ~(a ^ x)

刷题总结

  这个题目太有意思了,也是我遇到的第一题Python比C++和Java都要复杂的一题。这道题目推荐大家使用C++去写,没必要在Python的思路中绕来绕去。

-------------本文结束感谢您的阅读-------------
0%