问题描述:不使用+是或-操作符进行整数的加法运算

int getSum(int a, int b);

我的思路:把整数化成二进制进行运算,注意类型是int,也就是要考虑负数。关于负数的二进制表示可见之前的一篇博文

C语言的补码表示和unsigned及signed的转换

对于负数,我采用转换成正数(unsigned int,简称uint)的运算,也就是先实现uint的加法(plus)和减法(minus),再把int转换成uint进行运算。

一共4种情况,用实例说明,假使两个int绝对值为1和2,这4种情况及其处理如下:

1 + 2 = plus(1, 2) = 3

1 + (-2) = minus(1, 2) = -minus(2, 1) = -1

(-1) + 2 = minus(2, 1) = 1

(-1) + (-2) = -puls(1, 2) = -3

也就是说关键是uint的加减法。

第一步,要转换成二进制来运算,使用std::bitset

C++为了方面二进制运算提供了bitset<size_t>,http://www.cplusplus.com/reference/bitset/bitset/

模板参数为位数bits,32位/64位等。然后实现了[]的重载,operator[](int)返回一个伪引用类型或bool。

     bool operator[] (size_t pos) const;
reference operator[] (size_t pos);

其中reference类型实现了和bool的转换,以及operator~来进行反转(0变成1,1变成0)。参数pos是从最低位开始的,比如对bitset<32> i(2);那么i[1]就是1。

bitset可以接收unsigned long long作为构造参数,并且可以用to_ulong()方法返回unsigned long,以及to_ullong()方法返回unsigned long long。

第二步,二进制间加如何运算?

二进制的运算很简单,只需考虑11、10、01、00四种情况,但是对加法来说需要进位,对减法来说需要退位。

加法运算

用bool flag来判断是否进位,false(初始值)则不需要进位,true则在下一位运算时需要考虑进位。

flag为false时:01、10 => 1;00、11 => 0;其中11 => flag: true。

flag为true时:00 => 1, flag: false(完成进位);01、10 => 0;11 => 1(需要继续进位)。

直到进行到最高位的更高一位为止。

减法运算

同样用bool flag来判断是否退位,由于减法运算可能产生负数,需要事先判断a - b中a是否小于b,若小于b则需要交换,并且返回结果的相反数。

flag为false时:11、00 => 0; 10、01 => 1;其中01 => flag: true。

flag为true时:10 => 0, flag: false(完成退位);11、00 => 1,01 => 0(需要继续退位)。

解法:

class Solution {
public:
int getSum(int a, int b) {
if (a >= 0 && b >= 0)
return this->plus(a, b);
else if (a < 0 && b < 0)
return -this->plus(-a, -b);
else if (a >= 0 && b < 0)
return this->minus(a, -b);
else
return this->minus(b, -a);
}
private:
int plus(unsigned int a, unsigned int b) {
int n = (a >= b) ? (log2(a) + 2) : (log2(b) + 2);
bitset<32> binA(a), binB(b), res(0);
bool flag = false;
for (int i = 0; i < n; ++i) {
auto _a = binA[i];
auto _b = binB[i];
if (flag) {
res[i] = !(_a ^ _b);
flag = _a || _b; // (0,0):false
} else {
res[i] = _a ^ _b;
flag = _a && _b; // (1,1):true
}
}
return res.to_ulong();
}
int minus(unsigned int a, unsigned int b) {
if (a < b)
return -minus(b, a);
int n = log2(a) + 2;
bitset<32> binA(a), binB(b), res(0);
bool flag = false;
for (int i = 0; i < n; ++i) {
auto _a = binA[i];
auto _b = binB[i];
if (!(_a ^ _b))
res[i] = flag ? 1 : 0;
else { // (1,0) or (0,0)
res[i] = flag ? 0 : 1;
flag = (!_a) && _b; // (0,1):true (1,0):false
}
}
return res.to_ulong();
}
};

稍微细心点可以发现减法运算时我并没有先判断flag而是先判断_a ^ _b,对比下判断flag的代码就明白了

            if (flag) {
res[i] = !(_a ^ _b);
flag = !(_a & !_b); // (1,0):false
} else {
res[i] = _a ^ _b;
flag = (!_a) & _b; // (0,1):true
}

无论flag是true还是false,对flag不应该采取“重新计算”的态度,而是“是否改变”的态度,因为不进行操作的话flag的值就会传递到下次运算,也就是之前描述的【需要继续进位/退位】。如果进行一次操作来计算flag得到一样的结果是多此一举。

下面的代码测试用时是2ms,而上面的代码则是0ms,也是绝大多数人的用时。

更为简洁的解法:

    int getSum(int a, int b) {
int sum = a; while (b != 0)
{
sum = a ^ b;//calculate sum of a and b without thinking the carry
b = (a & b) << 1;//calculate the carry
a = sum;//add sum(without carry) and carry
} return sum;
}

这是看讨论区发现的https://discuss.leetcode.com/topic/49829/share-my-c-solutions-easy-to-understand/8

该做法并没有像我很自然想到的从低位到高位去算,而是进一步归纳,利用加法的本质,把不进位和进位运算分离开来。

关键性质:(a ^ b) + ((a & b) << 1) = a + b

举例来形象说明:

a = 11010 = 01000 + 10010 = a1 + common = a1 + (a & b)

b = 10011 = 00001 + 10010 = b1 + common = b1 + (a & b)

把两个数分解成2部分,common为对应位均为1的公共部分,由于11运算会导致进位,所以把它分离出来。

不进位的运算就是XOR运算,因为0 + 0 = 0 ^0 = 0, 0 + 1 = 0 ^ 1 = 1,并且不会产生进位。

而进位运算相当于2个common相加,common可以靠a&b求得,进位运算即(a & b) << 1。

迭代收敛的条件是a & b = 0,那么问题来了:最后为什么迭代会收敛?

反证法,什么时候迭代不会收敛?也就是b永远无法到达0。

暂且考虑正整数的情况:

设f(x)为x的二进制表示中1的个数,则f((a & b) << 1) = f(a & b) <= min{f(a), f(b)}。因为a&b是抽离出公共的1,而a、b至少有0个独有的1。

当且仅当a == b时取等号。

1、假设循环到某一步时a == b,则a ^ b = 0,下一步后a = 0。

再下一步,把(a & b) << 1的结果赋值给b,由于和0做AND运算会变成0,此时b为0,迭代收敛;

2、假设循环中一直a != b,那么new_b < old_b即new_b <= old_b - 1,由于b为整数,最后必定到达0,迭代收敛。

遗留问题:a、b为负数时的收敛证明?

【Leetcode 371】Sum of Two Integers的更多相关文章

  1. 【leetcode❤python】Sum Of Two Number

    #-*- coding: UTF-8 -*- #既然不能使用加法和减法,那么就用位操作.下面以计算5+4的例子说明如何用位操作实现加法:#1. 用二进制表示两个加数,a=5=0101,b=4=0100 ...

  2. 【LeetCode OJ】Sum Root to Leaf Numbers

    # Definition for a binary tree node # class TreeNode: # def __init__(self, x): # self.val = x # self ...

  3. 【leetcode❤python】 Sum of Left Leaves

    #-*- coding: UTF-8 -*- # Definition for a binary tree node.# class TreeNode(object):#     def __init ...

  4. 【LeetCode题解】二叉树的遍历

    我准备开始一个新系列[LeetCode题解],用来记录刷LeetCode题,顺便复习一下数据结构与算法. 1. 二叉树 二叉树(binary tree)是一种极为普遍的数据结构,树的每一个节点最多只有 ...

  5. 【LeetCode题解】136_只出现一次的数字

    目录 [LeetCode题解]136_只出现一次的数字 描述 方法一:列表操作 思路 Java 实现 Python 实现 方法二:哈希表 思路 Java 实现 Python 实现 方法三:数学运算 思 ...

  6. 【LeetCode题解】2_两数相加

    目录 [LeetCode题解]2_两数相加 描述 方法一:小学数学 思路 Java 代码(非递归写法) Java 代码(递归写法) Python 代码(非递归写法) [LeetCode题解]2_两数相 ...

  7. 【LeetCode 229】Majority Element II

    Given an integer array of size n, find all elements that appear more than ⌊ n/3 ⌋ times. The algorit ...

  8. 【LeetCode练习题】Permutation Sequence

    Permutation Sequence The set [1,2,3,…,n] contains a total of n! unique permutations. By listing and ...

  9. 【BZOJ3817/UOJ42】Sum(类欧)

    [BZOJ3817/UOJ42]Sum(类欧) 题面 BZOJ UOJ 题解 令\(x=\sqrt r\),那么要求的式子是\[\sum_{d=1}^n(-1)^{[dx]}\] 不难发现,对于每个\ ...

随机推荐

  1. 【nynu】 妹妹的工资怎么算(二分)

    题目链接:http://47.93.252.151/problem.php?id=1148 题目描述 <我的妹妹哪有这么可爱!>中的女主叫做高坂桐乃,高坂家的幺女,外表出众.成绩优秀.运动 ...

  2. 三、dbms_pipe(类似UNIX系统的管道)

    1.概述 说明:Oracle管道类似UNIX系统的管道,但不采用OS机制实现,管道信息被缓存到SGA中,当关闭例程时会丢失管道信息,建立公用管道所有数据库用户都可访问,私有管道只能由建立这访问.作用: ...

  3. HTML条件注释判断浏览器版本命令

    <!--[if !IE]><!--> 除IE外都可识别 <!--<![endif]--> <!--[if IE]> 所有的IE可识别 <![ ...

  4. MongoDB中MapReduce不同的写法,不同的结果

    MapReduce有两种写法,也可以说mongodb所有的命令行都有两种写法. 但突然间发现MapReduce不同的写法会有不同的结果,接下来我们一起来看: 第一种:直接使用扩展属性. 1)emit函 ...

  5. ES重要配置

    虽然ES需要的配置很少,但是仍然有些配置需要我们手工去配置,尤其是在产品上线之前. path.data and path.logs cluster.name node.name bootstrap.m ...

  6. 使用CGLib完成代理模式遇到的错误

    错误堆栈信息: Exception in thread "main" java.lang.NoClassDefFoundError: org/objectweb/asm/Type ...

  7. CDN加速的实现 --- varnish

    一.什么是CDN cdn全称为内容分发网络(Content Delivery Network).基本思想是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,是内容传输地更快.更稳定.通过在 ...

  8. EasyPusher RTSP推流/EasyRTMP RTMP推流Android安卓摄像头视频偏暗的问题解决方案

    本文转自EasyDarwin团队成员JOHN的博客:http://blog.csdn.net/jyt0551/article/details/75730226 在我们测试EasyPusher/Easy ...

  9. python重要模块之subprocess模块

    python重要模块之subprocess模块 我们经常要通过python去执行系统的命令或者脚本,系统的shell命令是独立于你的python进程之外的,每执行一条命令,就相当于发起了一个新的进程, ...

  10. php 配置上传大文件

    打开php.ini,首先找到file_uploads = on ;是否允许通过HTTP上传文件的开关.默认为ON即是开upload_tmp_dir ;文件上传至服务器上存储临时文件的地方,如果没指定就 ...