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

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. Find the odd int

    Given an array, find the int that appears an odd number of times. There will always be only one inte ...

  2. mysql和SqlServer 中取得汉字字段的各汉字首字母

    mysql 中取得汉字字段的各汉字首字母 这个转载于http://blog.csdn.net/lky5387/article/details/11973721 DELIMITER ;;CREATE  ...

  3. C++进阶2. typedef用法

    C++ 中的typedef用法 20131011 Typedef在C++中是一个关键字,他的用法有多重,但是自己又说不全面,所以整理一下: 1.用类型的别名 typedef char* PChar; ...

  4. java的简单入门,tomcat服务器

    Tomcat是一款开源的处理动态非常牛逼的web服务器.是sun公司开发的,在丧尸危机之后被收购了. 安装Tomcat需要的支持安装包 JDK下载:http://www.oracle.com/tech ...

  5. bzoj1269

    题解: splay维护 只不过变成了字符串 代码: #include<bits/stdc++.h> using namespace std; +,BS= + ,BN= + ; ,head, ...

  6. SpringInAction--Bean参数的自动注入

    前面我已经学过了,将一个bean引用注入到另一个bean的属性或构造器参数中,这边指的是将一个对象与另一个对象进行关联. 我们学过的方法是在创建的时候根据new对象的时候,注入参数,如下: @Bean ...

  7. selected多次点击不生效

    表单下拉选项使用selected设置选中时,发现第一次默认选中成功,在页面不刷新的情况下操作(比如ajax),虽然selected属性设置了,但是默认选中不生效. 解决办法1 可能是浏览器缓存问题,在 ...

  8. 【OpenCV入门教程之二】 一览众山小:OpenCV 2.4.8 or OpenCV 2.4.9组件结构全解析

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/19925819 作者:毛星云 ...

  9. HAWQ + MADlib 玩转数据挖掘之(二)——矩阵

    矩阵是Madlib中数据的基本格式,通常是二维的.在Madlib中,数组的概念与向量类似,数组通常是一维的,是矩阵的一种特殊形式. 一.矩阵表示 MADlib为矩阵提供了两种表示形式:稠密和稀疏. 1 ...

  10. Azure .Net应用架构原型

    本文介绍一种部署在azure 上.net 应用的一种常用架构对接者.这个角色可以是Api GateWay或代理或负载均衡器.目的有多方面,集中管理机器的注册和监控,安全,负载均衡,请求过滤,反向代理等 ...