数学计算模拟类问题:加法,除法和幂,注意越界问题。题 剑指Offer,Pow(x, n) ,Divide Two Integers
来源:互联网 发布:网络打印机添加xp 编辑:程序博客网 时间:2024/05/16 23:30
数学计算的模拟类题目,往往是要求实现某种计算(比如两数相除),实现的过程中会有所限定,比如不允许乘法等等。
这类题目首先要注意计算过程中本身的特殊情况。比如求相除,则必须首先反映过来除数不能为0。
其次要记得考虑负数的情况,如果计算范围不单单是整数,还要考虑double的比较方式。
最后要注意越界情况,这个是最容易犯错的,只能具体问题具体分析。
例题 1 不用"+ - * / "做加法
这道题来自于剑指Offer,为了归类,我把它放到了这里。
用位运算是第一反应,a^b可以算出当前位的结果, a&b可以算出是否进位。
但是接下来怎么办呢?
我的方法是,每次从两个数取一位,然后计算,进位。直到两个数都没有位可取了(往左看都只有0),最后做一个进位。
我的代码:
#include <stdio.h>int Sum(int a, int b){ int finalRes = 0; int count = 0; int prevAcc = 0, curAcc= 0; int p = 0, q = 0; int bitRes = 0; while(a | b){ p = a&1; q = b&1; a = a >> 1; b = b >> 1; int temp = p ^ q; curAcc = p & q; bitRes = temp ^ prevAcc; prevAcc = curAcc | (temp&prevAcc); finalRes |= (bitRes << (count++)); } finalRes |= prevAcc << count; return finalRes;}// ====================测试代码====================void Test(int num1, int num2, int expected){ int result = Sum(num1, num2); if(result == expected) printf("%d + %d is %d. Passed\n", num1, num2, result); else printf("%d + %d is %d. Failed\n", num1, num2, result);}int main(){ Test(1, 2, 3); Test(111, 899, 1010); Test(5, 2, 7); Test(1, 100, 101); Test(3, 0, 3); Test(0, 5, 5); Test(2, 8, 10);}
白板一次error free bug free,但是,写到一半就明白这个解法是不全面的。。
因为没有考虑负数。
补码表示下的负数,并不能通过每次提取一位的方法来倒腾。
这种思路对于上次facebook的面试题倒是可以用,当时的题目是:两个只含有'0' '1'的字符串,计算两个字符串相加的结果。
在这种情况下,答案的解法确实让人佩服。
书上代码:
int Add(int num1, int num2){ int sum, carry; do { sum = num1 ^ num2; carry = (num1 & num2) << 1; num1 = sum; num2 = carry; } while(num2 != 0); return num1;}
整体计算,carry表示各个位上计算后的进位,将carry左移一位,再加上sum,就是要求的结果。如何算carry和sum相加的结果呢?
同样可以利用上述方法。
循环思路出现。
循环终止条件是carry为0,也就是说,不再有进位了。
引申:
顺便提一提怎样在不定义新的变量的情况下交换两个int的方法。假设要交换a和b。
四则运算法,这个比较熟悉了。
a = a + b;
b = a - b;
a = a - b;
位运算法,不常用,但是更简单:
a = a ^ b;
b = a ^ b;
a = a ^ b;
例题 2 Pow(x, n)
我们需要的考虑的边界情况是:
1. 底数为0,指数为负数的情况。
2. 底数为零0,指数为0的情况。
情况2就是求0的0次幂,这个算是没有意义,返回0或者1均可,但是需要告知面试官你考虑到了,最好能打印出来这个情况。
同样,求幂时,我们没有必要使用时间复杂度为O(N)的连乘方式,这里N等于exponent的绝对值。而是可以使用递归的方式。
因为当n为偶数时,a^n = a^(n/2) * a^(n/2);
当n为奇数时,a^n = a^((n-1)/2) * a^((n-1)/2) * a。
时间复杂度为O(logN)
最后,在判断底数是否为0时,不能使用base == 0的判定方式,因为double和float类型在存储时都有误差,判断两个数是否相等,只要两者之差小于一个极小值,即可认为两者相等。
代码分为三个函数,这样更加清晰:
bool equalJudgeDouble(const double a, const double b){ if((a - b) < 0.00001 && (a - b) > -0.00001) return true; else return false;}double PowerWithUnsignedExponent(const double base, const unsigned int exp); if(exp == 0){ return 1; } double temp = 0.0; if(exp & 1 == 0){ temp = power(base, exp >> 1); return temp * temp; }else{ temp = power(base, (exp-1) >> 1); return temp * temp * base; }}double Power(const double base, const int exponent){ if(equalJudgeDouble(base, 0.0)){ if(exponent < 0); throw new std::Exception("Can not work when exponent is negative and base is 0."); return 0; } if(exponent < 0){ return 1.0 / PowerWithUnsignedExponent(base, (unsigned int)(-exponent)); }else if(exponent == 0){ return 1; }else{ return PowerWithUnsignedExponent(base, exponent); }}
例题 3 Divide Two Integers
Divide two integers without using multiplication, division and mod operator.
class Solution {public: int divide(int dividend, int divisor) { }};
这道题相较于前一道稍复杂些。首先考虑divisor为0的情况,再考虑负数的情况。
接着考虑解体方法,由于乘除都不能用,只能用加法,而如果直接累加自然会超时。我的思路是定义一个长32的数组path[32],path[0] = divisor, path[i] = path[i-1] + path[i-1]。path[32]不一定全被填满,当计算出path[i] > dividend时,path[i] 就不会被记录。由于path[i] 有大于 dividend的可能,因此临时存储计算结果的数定义为long long。
然后用这个path[32] 去凑成dividend,相除结果其实就是凑得过程中 1 << i 的相加(i 是 path[] 的 index)。
第一版代码如下:
class Solution {public: int divide(int dividend, int divisor) { if(divisor == 0) return 0; if(dividend == 0) return 0; bool minus1 = false, minus2 = false, minus = false; if(divisor < 0){ divisor = (0 - divisor); minus1 = true; } if(dividend < 0){ dividend = (0 - dividend); minus2 = true; } minus = (minus1 ^ minus2); //结果的正负号, minus若为true,结果就添加负号。 if(dividend < divisor) return 0; long long cache = divisor; int* path = new int[32]; int ind = 0; for(; cache <= dividend; path[ind] = (int)cache, ++ind, cache += cache); //填充path[] cache = path[--ind]; int res = 1 << ind; //从path的最末尾开始凑dividend while(ind >= 0){ if(cache == dividend) return minus ? (0-res) : res; if(cache > dividend){ cache -= path[ind]; res -= 1 << ind; } for(--ind; ind >= 0 && cache < dividend; cache += path[ind], res += 1 << ind); } return minus ? (0-res) : res; }};
这版代码在执行 sln.divide(-1010369383, -2147483648) 出现错误。
原因在于 开始的
dividend = (0 - dividend);
补码表示下,int下限绝对值比上限绝对值大1。dividend = -2147483648时,0-dividend 结果并非是2147483648。因此dividend 和 divisor的绝对值应该用unsigned int 表示。
考虑到这一点,当dividend = -2147483648 时,res 和 path 也有越过 int上限的可能,因此它们应该定义为 unisgned int。
改进后的代码,改动了一些参数的 格式,这次AC了。
class Solution {public: int divide(int dividend, int divisor) { if(divisor == 0) return 0; if(dividend == 0) return 0; bool minus1 = false, minus2 = false, minus = false; unsigned int divd, divr; if(divisor < 0){ divr = (0 - divisor); minus1 = true; }else{ divr = divisor; } if(dividend < 0){ divd = (0 - dividend); minus2 = true; }else{ divd = dividend; } minus = (minus1 ^ minus2); //结果的正负号, minus若为true,结果就添加负号。 if(divd < divr) return 0; long long cache = divr; unsigned int* path = new unsigned int[32]; int ind = 0; for(; cache <= divd; path[ind] = (unsigned int)cache, ++ind, cache += cache); //填充path[] cache = path[--ind]; unsigned int res = 1 << ind; //从path的最末尾开始凑dividend while(ind >= 0){ if(cache == divd) return minus ? (0-res) : res; if(cache > divd){ cache -= path[ind]; res -= 1 << ind; } for(--ind; ind >= 0 && cache < divd; cache += path[ind], res += 1 << ind); } return minus ? (0-res) : res; }};
- 数学计算模拟类问题:加法,除法和幂,注意越界问题。题 剑指Offer,Pow(x, n) ,Divide Two Integers
- leetcode002-数值处理Reverse Integer,Pow(x,n), atoi, Divide Two Integers
- 数学问题2,求Pow(x, n)
- Divide Two Integers:实现除法
- Divide Two Integers问题及解法
- LeetCode 29. Divide Two Integers(除法)
- leetcode 29. Divide Two Integers 做除法
- LeetCode问题 pow(x,n)
- LeetCode问题 pow(x,n)
- LeetCode问题 pow(x,n)
- Divide Two Integers不使用乘除法来计算两个数相除
- 剑指Offer 面试题11:数的整数次方(Leetcode50. Pow(x, n))【C库函数pow模拟】题解
- Pow(x, n)问题及解法
- [LeeCode]-Divide Two Integers 不用乘除的除法运算
- Divide Two Integers 不使用乘除取余求除法
- LeetCode 29 Divide Two Integers 除法的原理
- [leetcode刷题系列]Divide Two Integers
- 第69题 Divide Two Integers
- Java NIO:浅析I/O模型
- Android自带的toolbox分析及扩展
- 【poj 1502】 MPI Maelstrom 【East Central North America 1996】
- Travel HDU
- Can't create handler inside thread that has not called Looper.prepare()
- 数学计算模拟类问题:加法,除法和幂,注意越界问题。题 剑指Offer,Pow(x, n) ,Divide Two Integers
- 菜鸟学框架——Mybatis核心组件
- 4:如何实现对两个整数变量的值进行互换。
- Linux/Centos:如何安装GCC(C和C++编译器)
- pymysql+Flask-SQLAlchemy连接mysql
- 我眼中的数据仓库
- Android_RecycleView基本详解
- Linux笔记(二)常用命令
- 关于LVS_DR模型的工作流程